1 ## compare_shells: bash dash mksh
2 ## oils_failures_allowed: 1
3 ## tags: interactive
4
5 # Test options to set, shopt, $SH.
6
7 #### $- with -c
8 # dash's behavior seems most sensible here?
9 $SH -o nounset -c 'echo $-'
10 ## stdout: u
11 ## OK bash stdout: huBc
12 ## OK mksh stdout: uhc
13 ## status: 0
14
15 #### $- with pipefail
16 set -o pipefail -o nounset
17 echo $-
18 ## stdout: u
19 ## status: 0
20 ## OK bash stdout: huBs
21 ## OK mksh stdout: ush
22 ## N-I dash stdout-json: ""
23 ## N-I dash status: 2
24
25 #### $- and more options
26 set -efuC
27 o=$-
28 [[ $o == *e* ]]; echo yes
29 [[ $o == *f* ]]; echo yes
30 [[ $o == *u* ]]; echo yes
31 [[ $o == *C* ]]; echo yes
32 ## STDOUT:
33 yes
34 yes
35 yes
36 yes
37 ## END
38 ## N-I dash stdout-json: ""
39 ## N-I dash status: 127
40
41 #### $- with interactive shell
42 $SH -c 'echo $-' | grep i || echo FALSE
43 $SH -i -c 'echo $-' | grep -q i && echo TRUE
44 ## STDOUT:
45 FALSE
46 TRUE
47 ## END
48 #### pass short options like sh -e
49 $SH -e -c 'false; echo status=$?'
50 ## stdout-json: ""
51 ## status: 1
52
53 #### pass long options like sh -o errexit
54 $SH -o errexit -c 'false; echo status=$?'
55 ## stdout-json: ""
56 ## status: 1
57
58 #### pass shopt options like sh -O nullglob
59 $SH +O nullglob -c 'echo foo *.nonexistent bar'
60 $SH -O nullglob -c 'echo foo *.nonexistent bar'
61 ## STDOUT:
62 foo *.nonexistent bar
63 foo bar
64 ## END
65 ## N-I dash/mksh stdout-json: ""
66 ## N-I dash status: 2
67 ## N-I mksh status: 1
68
69 #### can continue after unknown option
70 # dash and mksh make this a fatal error no matter what.
71 set -o errexit
72 set -o STRICT || true # unknown option
73 echo hello
74 ## stdout: hello
75 ## status: 0
76 ## BUG dash/mksh stdout-json: ""
77 ## BUG dash status: 2
78 ## BUG mksh status: 1
79
80 #### set with both options and argv
81 set -o errexit a b c
82 echo "$@"
83 false
84 echo done
85 ## stdout: a b c
86 ## status: 1
87
88 #### set -o vi/emacs
89 set -o vi
90 echo $?
91 set -o emacs
92 echo $?
93 ## STDOUT:
94 0
95 0
96 ## END
97
98 #### vi and emacs are mutually exclusive
99 show() {
100 shopt -o -p | egrep 'emacs$|vi$'
101 echo ___
102 };
103 show
104
105 set -o emacs
106 show
107
108 set -o vi
109 show
110
111 ## STDOUT:
112 set +o emacs
113 set +o vi
114 ___
115 set -o emacs
116 set +o vi
117 ___
118 set +o emacs
119 set -o vi
120 ___
121 ## END
122 ## N-I dash/mksh STDOUT:
123 ___
124 ___
125 ___
126 ## END
127
128 #### interactive shell starts with emacs mode on
129 case $SH in dash) exit ;; esac
130 case $SH in bash|*osh) flag='--rcfile /dev/null' ;; esac
131
132 code='test -o emacs; echo $?; test -o vi; echo $?'
133
134 echo non-interactive
135 $SH $flag -c "$code"
136
137 echo interactive
138 $SH $flag -i -c "$code"
139
140 ## STDOUT:
141 non-interactive
142 1
143 1
144 interactive
145 0
146 1
147 ## END
148 ## OK mksh STDOUT:
149 non-interactive
150 0
151 1
152 interactive
153 0
154 1
155 ## END
156 ## N-I dash stdout-json: ""
157
158 #### nounset
159 echo "[$unset]"
160 set -o nounset
161 echo "[$unset]"
162 echo end # never reached
163 ## stdout: []
164 ## status: 1
165 ## OK dash status: 2
166
167 #### -u is nounset
168 echo "[$unset]"
169 set -u
170 echo "[$unset]"
171 echo end # never reached
172 ## stdout: []
173 ## status: 1
174 ## OK dash status: 2
175
176 #### nounset with "$@"
177 set a b c
178 set -u # shouldn't touch argv
179 echo "$@"
180 ## stdout: a b c
181
182 #### set -u -- clears argv
183 set a b c
184 set -u -- # shouldn't touch argv
185 echo "$@"
186 ## stdout:
187
188 #### set -u -- x y z
189 set a b c
190 set -u -- x y z
191 echo "$@"
192 ## stdout: x y z
193
194 #### reset option with long flag
195 set -o errexit
196 set +o errexit
197 echo "[$unset]"
198 ## stdout: []
199 ## status: 0
200
201 #### reset option with short flag
202 set -u
203 set +u
204 echo "[$unset]"
205 ## stdout: []
206 ## status: 0
207
208 #### set -eu (flag parsing)
209 set -eu
210 echo "[$unset]"
211 echo status=$?
212 ## stdout-json: ""
213 ## status: 1
214 ## OK dash status: 2
215
216 #### -n for no execution (useful with --ast-output)
217 # NOTE: set +n doesn't work because nothing is executed!
218 echo 1
219 set -n
220 echo 2
221 set +n
222 echo 3
223 # osh doesn't work because it only checks -n in bin/oil.py?
224 ## STDOUT:
225 1
226 ## END
227 ## status: 0
228
229 #### pipefail
230 # NOTE: the sleeps are because osh can fail non-deterministically because of a
231 # bug. Same problem as PIPESTATUS.
232 { sleep 0.01; exit 9; } | { sleep 0.02; exit 2; } | { sleep 0.03; }
233 echo $?
234 set -o pipefail
235 { sleep 0.01; exit 9; } | { sleep 0.02; exit 2; } | { sleep 0.03; }
236 echo $?
237 ## STDOUT:
238 0
239 2
240 ## END
241 ## status: 0
242 ## N-I dash STDOUT:
243 0
244 ## END
245 ## N-I dash status: 2
246
247 #### shopt -p -o prints 'set' options
248 case $SH in dash|mksh) exit ;; esac
249
250 shopt -po nounset
251 set -o nounset
252 shopt -po nounset
253
254 echo --
255
256 shopt -po | egrep -o 'errexit|noglob|nounset'
257
258 ## STDOUT:
259 set +o nounset
260 set -o nounset
261 --
262 errexit
263 noglob
264 nounset
265 ## END
266 ## N-I dash/mksh STDOUT:
267 ## END
268
269 #### shopt -o prints 'set' options
270 case $SH in dash|mksh) exit ;; esac
271
272 shopt -o | egrep -o 'errexit|noglob|nounset'
273 echo --
274 ## STDOUT:
275 errexit
276 noglob
277 nounset
278 --
279 ## END
280 ## N-I dash/mksh STDOUT:
281 ## END
282
283 #### shopt -p prints 'shopt' options
284 shopt -p nullglob
285 shopt -s nullglob
286 shopt -p nullglob
287 ## STDOUT:
288 shopt -u nullglob
289 shopt -s nullglob
290 ## END
291 ## N-I dash/mksh stdout-json: ""
292 ## N-I dash/mksh status: 127
293
294 #### shopt with no flags prints options
295 cd $TMP
296
297 # print specific options. OSH does it in a different format.
298 shopt nullglob failglob > one.txt
299 wc -l one.txt
300 grep -o nullglob one.txt
301 grep -o failglob one.txt
302
303 # print all options
304 shopt | grep nullglob | wc -l
305 ## STDOUT:
306 2 one.txt
307 nullglob
308 failglob
309 1
310 ## END
311 ## N-I dash/mksh STDOUT:
312 0 one.txt
313 0
314 ## END
315
316 #### noclobber off
317 set -o errexit
318
319 echo foo > can-clobber
320 echo status=$?
321 set +C
322
323 echo foo > can-clobber
324 echo status=$?
325 set +o noclobber
326
327 echo foo > can-clobber
328 echo status=$?
329 cat can-clobber
330
331 ## STDOUT:
332 status=0
333 status=0
334 status=0
335 foo
336 ## END
337
338 #### noclobber on
339
340 rm -f no-clobber
341 set -C
342
343 echo foo > no-clobber
344 echo create=$?
345
346 echo overwrite > no-clobber
347 echo overwrite=$?
348
349 echo force >| no-clobber
350 echo force=$?
351
352 cat no-clobber
353
354 ## STDOUT:
355 create=0
356 overwrite=1
357 force=0
358 force
359 ## END
360 ## OK dash STDOUT:
361 create=0
362 overwrite=2
363 force=0
364 force
365 ## END
366
367 #### noclobber on <>
368 set -C
369 echo foo >| $TMP/no-clobber
370 exec 3<> $TMP/no-clobber
371 read -n 1 <&3
372 echo -n . >&3
373 exec 3>&-
374 cat $TMP/no-clobber
375 ## STDOUT:
376 f.o
377 ## END
378 ## N-I dash STDOUT:
379 .oo
380 ## END
381
382 #### set - -
383 set a b
384 echo "$@"
385 set - a b
386 echo "$@"
387 set -- a b
388 echo "$@"
389 set - -
390 echo "$@"
391 set - +
392 echo "$@"
393 set + -
394 echo "$@"
395 set -- --
396 echo "$@"
397
398 # note: zsh is different, and yash is totally different
399 ## STDOUT:
400 a b
401 a b
402 a b
403 -
404 +
405 +
406 --
407 ## END
408 ## OK osh/yash STDOUT:
409 a b
410 - a b
411 a b
412 - -
413 - +
414 + -
415 --
416 ## END
417 ## BUG mksh STDOUT:
418 a b
419 a b
420 a b
421 -
422 +
423 -
424 --
425 ## END
426 ## BUG zsh STDOUT:
427 a b
428 a b
429 a b
430
431 +
432
433 --
434 ## END
435
436 #### set -o lists options
437 # NOTE: osh doesn't use the same format yet.
438 set -o | grep -o noexec
439 ## STDOUT:
440 noexec
441 ## END
442
443 #### set without args lists variables
444 __GLOBAL=g
445 f() {
446 local __mylocal=L
447 local __OTHERLOCAL=L
448 __GLOBAL=mutated
449 set | grep '^__'
450 }
451 g() {
452 local __var_in_parent_scope=D
453 f
454 }
455 g
456 ## status: 0
457 ## STDOUT:
458 __GLOBAL=mutated
459 __OTHERLOCAL=L
460 __mylocal=L
461 __var_in_parent_scope=D
462 ## END
463 ## OK mksh STDOUT:
464 __GLOBAL=mutated
465 __var_in_parent_scope=D
466 __OTHERLOCAL=L
467 __mylocal=L
468 ## END
469 ## OK dash STDOUT:
470 __GLOBAL='mutated'
471 __OTHERLOCAL='L'
472 __mylocal='L'
473 __var_in_parent_scope='D'
474 ## END
475
476 #### 'set' and 'eval' round trip
477
478 # NOTE: not testing arrays and associative arrays!
479 _space='[ ]'
480 _whitespace=$'[\t\r\n]'
481 _sq="'single quotes'"
482 _backslash_dq="\\ \""
483 _unicode=$'[\u03bc]'
484
485 # Save the variables
486 varfile=$TMP/vars-$(basename $SH).txt
487
488 set | grep '^_' > "$varfile"
489
490 # Unset variables
491 unset _space _whitespace _sq _backslash_dq _unicode
492 echo [ $_space $_whitespace $_sq $_backslash_dq $_unicode ]
493
494 # Restore them
495
496 . $varfile
497 echo "Code saved to $varfile" 1>&2 # for debugging
498
499 test "$_space" = '[ ]' && echo OK
500 test "$_whitespace" = $'[\t\r\n]' && echo OK
501 test "$_sq" = "'single quotes'" && echo OK
502 test "$_backslash_dq" = "\\ \"" && echo OK
503 test "$_unicode" = $'[\u03bc]' && echo OK
504
505 ## STDOUT:
506 [ ]
507 OK
508 OK
509 OK
510 OK
511 OK
512 ## END
513
514 #### set without args and array variables
515 declare -a __array
516 __array=(1 2 '3 4')
517 set | grep '^__'
518 ## STDOUT:
519 __array=(1 2 '3 4')
520 ## END
521 ## OK bash STDOUT:
522 __array=([0]="1" [1]="2" [2]="3 4")
523 ## END
524 ## OK mksh STDOUT:
525 __array[0]=1
526 __array[1]=2
527 __array[2]='3 4'
528 ## END
529 ## OK zsh STDOUT:
530 a=( 1 2 3 )
531 ## END
532 ## N-I dash stdout-json: ""
533 ## N-I dash status: 2
534
535 #### set without args and assoc array variables (not in OSH)
536 typeset -A __assoc
537 __assoc['k e y']='v a l'
538 __assoc[a]=b
539 set | grep '^__'
540 ## STDOUT:
541 __assoc=([a]="b" ["k e y"]="v a l" )
542 ## END
543 ## N-I mksh stdout-json: ""
544 ## N-I mksh status: 1
545 ## N-I dash stdout-json: ""
546 ## N-I dash status: 1
547 ## N-I osh stdout-json: ""
548 ## N-I osh status: 1
549
550 #### shopt -q
551 shopt -q nullglob
552 echo nullglob=$?
553
554 # set it
555 shopt -s nullglob
556
557 shopt -q nullglob
558 echo nullglob=$?
559
560 shopt -q nullglob failglob
561 echo nullglob,failglob=$?
562
563 # set it
564 shopt -s failglob
565 shopt -q nullglob failglob
566 echo nullglob,failglob=$?
567
568 ## STDOUT:
569 nullglob=1
570 nullglob=0
571 nullglob,failglob=1
572 nullglob,failglob=0
573 ## END
574 ## N-I dash/mksh STDOUT:
575 nullglob=127
576 nullglob=127
577 nullglob,failglob=127
578 nullglob,failglob=127
579 ## END
580
581 #### shopt -q invalid
582 shopt -q invalidZZ
583 echo invalidZZ=$?
584 ## STDOUT:
585 invalidZZ=2
586 ## END
587 ## OK bash STDOUT:
588 invalidZZ=1
589 ## END
590 ## N-I dash/mksh STDOUT:
591 invalidZZ=127
592 ## END
593
594 #### shopt -s strict:all
595 n=2
596
597 show-strict() {
598 shopt -p | grep 'strict_' | head -n $n
599 echo -
600 }
601
602 show-strict
603 shopt -s strict:all
604 show-strict
605 shopt -u strict_arith
606 show-strict
607 ## STDOUT:
608 shopt -u strict_argv
609 shopt -u strict_arith
610 -
611 shopt -s strict_argv
612 shopt -s strict_arith
613 -
614 shopt -s strict_argv
615 shopt -u strict_arith
616 -
617 ## END
618 ## N-I dash status: 2
619 ## N-I dash stdout-json: ""
620 ## N-I bash/mksh STDOUT:
621 -
622 -
623 -
624 ## END
625
626 #### shopt allows for backward compatibility like bash
627
628 # doesn't have to be on, but just for testing
629 set -o errexit
630
631 shopt -p nullglob || true # bash returns 1 here? Like -q.
632
633 # This should set nullglob, and return 1, which can be ignored
634 shopt -s nullglob strict_OPTION_NOT_YET_IMPLEMENTED 2>/dev/null || true
635 echo status=$?
636
637 shopt -p nullglob || true
638
639 ## STDOUT:
640 shopt -u nullglob
641 status=0
642 shopt -s nullglob
643 ## END
644 ## N-I dash/mksh STDOUT:
645 status=0
646 ## END
647 ## N-I dash/mksh status: 0
648
649 #### shopt -p validates option names
650 shopt -p nullglob invalid failglob
651 echo status=$?
652 # same thing as -p, slightly different format in bash
653 shopt nullglob invalid failglob > $TMP/out.txt
654 status=$?
655 sed --regexp-extended 's/\s+/ /' $TMP/out.txt # make it easier to assert
656 echo status=$status
657 ## STDOUT:
658 status=2
659 status=2
660 ## END
661 ## OK bash STDOUT:
662 shopt -u nullglob
663 shopt -u failglob
664 status=1
665 nullglob off
666 failglob off
667 status=1
668 ## END
669 ## N-I dash/mksh STDOUT:
670 status=127
671 status=127
672 ## END
673
674 #### shopt -p -o validates option names
675 shopt -p -o errexit invalid nounset
676 echo status=$?
677 ## STDOUT:
678 set +o errexit
679 status=2
680 ## END
681 ## OK bash STDOUT:
682 set +o errexit
683 set +o nounset
684 status=1
685 ## END
686 ## N-I dash/mksh STDOUT:
687 status=127
688 ## END
689
690 #### stubbed out bash options
691 shopt -s ignore_shopt_not_impl
692 for name in foo autocd cdable_vars checkwinsize; do
693 shopt -s $name
694 echo $?
695 done
696 ## STDOUT:
697 2
698 0
699 0
700 0
701 ## END
702 ## OK bash STDOUT:
703 1
704 0
705 0
706 0
707 ## END
708 ## OK dash/mksh STDOUT:
709 127
710 127
711 127
712 127
713 ## END
714
715 #### shopt -s nounset works in YSH, not in bash
716 case $SH in
717 *dash|*mksh)
718 echo N-I
719 exit
720 ;;
721 esac
722 shopt -s nounset
723 echo status=$?
724
725 # get rid of extra space in bash output
726 set -o | grep nounset | sed 's/[ \t]\+/ /g'
727
728 ## STDOUT:
729 status=0
730 set -o nounset
731 ## END
732 ## OK bash STDOUT:
733 status=1
734 nounset off
735 # END
736 ## N-I dash/mksh STDOUT:
737 N-I
738 ## END
739
740 #### Unimplemented options - print, query, set, unset
741 case $SH in dash|mksh) exit ;; esac
742
743 opt_name=xpg_echo
744
745 shopt -p xpg_echo
746 shopt -q xpg_echo; echo q=$?
747
748 shopt -s xpg_echo
749 shopt -p xpg_echo
750
751 shopt -u xpg_echo
752 shopt -p xpg_echo
753 echo p=$? # weird, bash also returns a status
754
755 shopt xpg_echo >/dev/null
756 echo noflag=$?
757
758 shopt -o errexit >/dev/null
759 echo set=$?
760
761 ## STDOUT:
762 q=2
763 p=2
764 noflag=2
765 set=1
766 ## END
767
768 ## OK bash STDOUT:
769 shopt -u xpg_echo
770 q=1
771 shopt -s xpg_echo
772 shopt -u xpg_echo
773 p=1
774 noflag=1
775 set=1
776 ## END
777
778 ## N-I dash/mksh STDOUT:
779 ## END
780
781 #### Unimplemented options - OSH shopt -s ignore_shopt_not_impl
782 case $SH in dash|mksh) exit ;; esac
783
784 shopt -s ignore_shopt_not_impl
785
786 opt_name=xpg_echo
787
788 shopt -p xpg_echo
789 shopt -q xpg_echo; echo q=$?
790
791 shopt -s xpg_echo
792 shopt -p xpg_echo
793
794 shopt -u xpg_echo
795 shopt -p xpg_echo
796 echo p=$? # weird, bash also returns a status
797
798 shopt xpg_echo >/dev/null
799 echo noflag=$?
800
801 shopt -o errexit >/dev/null
802 echo set=$?
803
804 ## STDOUT:
805 shopt -u xpg_echo
806 q=1
807 shopt -s xpg_echo
808 shopt -u xpg_echo
809 p=1
810 noflag=1
811 set=1
812 ## END
813
814 ## N-I dash/mksh STDOUT:
815 ## END
816
817 #### shopt -p exit code (regression)
818 case $SH in dash|mksh) exit ;; esac
819
820 shopt -p > /dev/null
821 echo status=$?
822
823 ## STDOUT:
824 status=0
825 ## END
826
827 ## N-I dash/mksh STDOUT:
828 ## END
829
830 #### no-ops not shown by shopt -p
831
832 shopt -p | grep xpg
833 echo --
834 ## STDOUT:
835 --
836 ## END
837 ## OK bash STDOUT:
838 shopt -u xpg_echo
839 --
840 ## END
841
842