1 ## compare_shells: bash dash mksh
2 ## oils_failures_allowed: 3
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-json: "f.o\n"
376 ## N-I dash stdout-json: ".oo\n"
377
378 #### SHELLOPTS is updated when options are changed
379 echo $SHELLOPTS | grep -q xtrace
380 echo $?
381 set -x
382 echo $SHELLOPTS | grep -q xtrace
383 echo $?
384 set +x
385 echo $SHELLOPTS | grep -q xtrace
386 echo $?
387 ## STDOUT:
388 1
389 0
390 1
391 ## END
392 ## N-I dash/mksh STDOUT:
393 1
394 1
395 1
396 ## END
397
398 #### SHELLOPTS is readonly
399 SHELLOPTS=x
400 echo status=$?
401 ## stdout: status=1
402 ## N-I dash/mksh stdout: status=0
403
404 # Setting a readonly variable in osh is a hard failure.
405 ## OK osh status: 1
406 ## OK osh stdout-json: ""
407
408 #### SHELLOPTS and BASHOPTS are set
409
410 # 2024-06 - tickled by Samuel testing Gentoo
411
412 # bash: bracexpand:hashall etc.
413
414 echo shellopts ${SHELLOPTS:?} > /dev/null
415 echo bashopts ${BASHOPTS:?} > /dev/null
416
417 ## STDOUT:
418 ## END
419
420 ## N-I dash status: 2
421 ## N-I mksh status: 1
422
423
424 #### set - -
425 set a b
426 echo "$@"
427 set - a b
428 echo "$@"
429 set -- a b
430 echo "$@"
431 set - -
432 echo "$@"
433 set - +
434 echo "$@"
435 set + -
436 echo "$@"
437 set -- --
438 echo "$@"
439
440 # note: zsh is different, and yash is totally different
441 ## STDOUT:
442 a b
443 a b
444 a b
445 -
446 +
447 +
448 --
449 ## END
450 ## OK osh/yash STDOUT:
451 a b
452 - a b
453 a b
454 - -
455 - +
456 + -
457 --
458 ## END
459 ## BUG mksh STDOUT:
460 a b
461 a b
462 a b
463 -
464 +
465 -
466 --
467 ## END
468 ## BUG zsh STDOUT:
469 a b
470 a b
471 a b
472
473 +
474
475 --
476 ## END
477
478 #### set -o lists options
479 # NOTE: osh doesn't use the same format yet.
480 set -o | grep -o noexec
481 ## STDOUT:
482 noexec
483 ## END
484
485 #### set without args lists variables
486 __GLOBAL=g
487 f() {
488 local __mylocal=L
489 local __OTHERLOCAL=L
490 __GLOBAL=mutated
491 set | grep '^__'
492 }
493 g() {
494 local __var_in_parent_scope=D
495 f
496 }
497 g
498 ## status: 0
499 ## STDOUT:
500 __GLOBAL=mutated
501 __OTHERLOCAL=L
502 __mylocal=L
503 __var_in_parent_scope=D
504 ## END
505 ## OK mksh STDOUT:
506 __GLOBAL=mutated
507 __var_in_parent_scope=D
508 __OTHERLOCAL=L
509 __mylocal=L
510 ## END
511 ## OK dash STDOUT:
512 __GLOBAL='mutated'
513 __OTHERLOCAL='L'
514 __mylocal='L'
515 __var_in_parent_scope='D'
516 ## END
517
518 #### 'set' and 'eval' round trip
519
520 # NOTE: not testing arrays and associative arrays!
521 _space='[ ]'
522 _whitespace=$'[\t\r\n]'
523 _sq="'single quotes'"
524 _backslash_dq="\\ \""
525 _unicode=$'[\u03bc]'
526
527 # Save the variables
528 varfile=$TMP/vars-$(basename $SH).txt
529
530 set | grep '^_' > "$varfile"
531
532 # Unset variables
533 unset _space _whitespace _sq _backslash_dq _unicode
534 echo [ $_space $_whitespace $_sq $_backslash_dq $_unicode ]
535
536 # Restore them
537
538 . $varfile
539 echo "Code saved to $varfile" 1>&2 # for debugging
540
541 test "$_space" = '[ ]' && echo OK
542 test "$_whitespace" = $'[\t\r\n]' && echo OK
543 test "$_sq" = "'single quotes'" && echo OK
544 test "$_backslash_dq" = "\\ \"" && echo OK
545 test "$_unicode" = $'[\u03bc]' && echo OK
546
547 ## STDOUT:
548 [ ]
549 OK
550 OK
551 OK
552 OK
553 OK
554 ## END
555
556 #### set without args and array variables (not in OSH)
557 declare -a __array
558 __array=(1 2 '3 4')
559 set | grep '^__'
560 ## STDOUT:
561 __array=([0]="1" [1]="2" [2]="3 4")
562 ## END
563 ## OK mksh STDOUT:
564 __array[0]=1
565 __array[1]=2
566 __array[2]='3 4'
567 ## N-I dash stdout-json: ""
568 ## N-I dash status: 2
569 ## N-I osh stdout-json: ""
570 ## N-I osh status: 1
571
572 #### set without args and assoc array variables (not in OSH)
573 typeset -A __assoc
574 __assoc['k e y']='v a l'
575 __assoc[a]=b
576 set | grep '^__'
577 ## STDOUT:
578 __assoc=([a]="b" ["k e y"]="v a l" )
579 ## END
580 ## N-I mksh stdout-json: ""
581 ## N-I mksh status: 1
582 ## N-I dash stdout-json: ""
583 ## N-I dash status: 1
584 ## N-I osh stdout-json: ""
585 ## N-I osh status: 1
586
587 #### shopt -q
588 shopt -q nullglob
589 echo nullglob=$?
590
591 # set it
592 shopt -s nullglob
593
594 shopt -q nullglob
595 echo nullglob=$?
596
597 shopt -q nullglob failglob
598 echo nullglob,failglob=$?
599
600 # set it
601 shopt -s failglob
602 shopt -q nullglob failglob
603 echo nullglob,failglob=$?
604
605 ## STDOUT:
606 nullglob=1
607 nullglob=0
608 nullglob,failglob=1
609 nullglob,failglob=0
610 ## END
611 ## N-I dash/mksh STDOUT:
612 nullglob=127
613 nullglob=127
614 nullglob,failglob=127
615 nullglob,failglob=127
616 ## END
617
618 #### shopt -q invalid
619 shopt -q invalidZZ
620 echo invalidZZ=$?
621 ## STDOUT:
622 invalidZZ=2
623 ## END
624 ## OK bash STDOUT:
625 invalidZZ=1
626 ## END
627 ## N-I dash/mksh STDOUT:
628 invalidZZ=127
629 ## END
630
631 #### shopt -s strict:all
632 n=2
633
634 show-strict() {
635 shopt -p | grep 'strict_' | head -n $n
636 echo -
637 }
638
639 show-strict
640 shopt -s strict:all
641 show-strict
642 shopt -u strict_arith
643 show-strict
644 ## STDOUT:
645 shopt -u strict_argv
646 shopt -u strict_arith
647 -
648 shopt -s strict_argv
649 shopt -s strict_arith
650 -
651 shopt -s strict_argv
652 shopt -u strict_arith
653 -
654 ## END
655 ## N-I dash status: 2
656 ## N-I dash stdout-json: ""
657 ## N-I bash/mksh STDOUT:
658 -
659 -
660 -
661 ## END
662
663 #### shopt allows for backward compatibility like bash
664
665 # doesn't have to be on, but just for testing
666 set -o errexit
667
668 shopt -p nullglob || true # bash returns 1 here? Like -q.
669
670 # This should set nullglob, and return 1, which can be ignored
671 shopt -s nullglob strict_OPTION_NOT_YET_IMPLEMENTED 2>/dev/null || true
672 echo status=$?
673
674 shopt -p nullglob || true
675
676 ## STDOUT:
677 shopt -u nullglob
678 status=0
679 shopt -s nullglob
680 ## END
681 ## N-I dash/mksh STDOUT:
682 status=0
683 ## END
684 ## N-I dash/mksh status: 0
685
686 #### shopt -p validates option names
687 shopt -p nullglob invalid failglob
688 echo status=$?
689 # same thing as -p, slightly different format in bash
690 shopt nullglob invalid failglob > $TMP/out.txt
691 status=$?
692 sed --regexp-extended 's/\s+/ /' $TMP/out.txt # make it easier to assert
693 echo status=$status
694 ## STDOUT:
695 status=2
696 status=2
697 ## END
698 ## OK bash STDOUT:
699 shopt -u nullglob
700 shopt -u failglob
701 status=1
702 nullglob off
703 failglob off
704 status=1
705 ## END
706 ## N-I dash/mksh STDOUT:
707 status=127
708 status=127
709 ## END
710
711 #### shopt -p -o validates option names
712 shopt -p -o errexit invalid nounset
713 echo status=$?
714 ## STDOUT:
715 set +o errexit
716 status=2
717 ## END
718 ## OK bash STDOUT:
719 set +o errexit
720 set +o nounset
721 status=1
722 ## END
723 ## N-I dash/mksh STDOUT:
724 status=127
725 ## END
726
727 #### stubbed out bash options
728 for name in foo autocd cdable_vars checkwinsize; do
729 shopt -s $name
730 echo $?
731 done
732 ## STDOUT:
733 2
734 0
735 0
736 0
737 ## END
738 ## OK bash STDOUT:
739 1
740 0
741 0
742 0
743 ## END
744 ## OK dash/mksh STDOUT:
745 127
746 127
747 127
748 127
749 ## END
750
751 #### shopt -s nounset works in YSH, not in bash
752 case $SH in
753 *dash|*mksh)
754 echo N-I
755 exit
756 ;;
757 esac
758 shopt -s nounset
759 echo status=$?
760
761 # get rid of extra space in bash output
762 set -o | grep nounset | sed 's/[ \t]\+/ /g'
763
764 ## STDOUT:
765 status=0
766 set -o nounset
767 ## END
768 ## OK bash STDOUT:
769 status=1
770 nounset off
771 # END
772 ## N-I dash/mksh STDOUT:
773 N-I
774 ## END
775
776 #### Unimplemented options - print, query, set, unset
777 case $SH in dash|mksh) exit ;; esac
778
779 opt_name=xpg_echo
780
781 shopt -p xpg_echo
782 shopt -q xpg_echo; echo q=$?
783
784 shopt -s xpg_echo
785 shopt -p xpg_echo
786
787 shopt -u xpg_echo
788 shopt -p xpg_echo
789 echo p=$? # weird, bash also returns a status
790
791 shopt xpg_echo >/dev/null
792 echo noflag=$?
793
794 shopt -o errexit >/dev/null
795 echo set=$?
796
797 ## STDOUT:
798 TODO
799 ## END
800
801 ## OK bash STDOUT:
802 shopt -u xpg_echo
803 q=1
804 shopt -s xpg_echo
805 shopt -u xpg_echo
806 p=1
807 noflag=1
808 set=1
809 ## END
810
811 ## N-I dash/mksh STDOUT:
812 ## END
813
814 #### Unimplemented options - OSH shopt -s ignore_shopt_not_impl
815 case $SH in dash|mksh) exit ;; esac
816
817 shopt -s ignore_shopt_not_impl
818
819 opt_name=xpg_echo
820
821 shopt -p xpg_echo
822 shopt -q xpg_echo; echo q=$?
823
824 shopt -s xpg_echo
825 shopt -p xpg_echo
826
827 shopt -u xpg_echo
828 shopt -p xpg_echo
829 echo p=$? # weird, bash also returns a status
830
831 shopt xpg_echo >/dev/null
832 echo noflag=$?
833
834 shopt -o errexit >/dev/null
835 echo set=$?
836
837 ## STDOUT:
838 shopt -u xpg_echo
839 q=1
840 shopt -s xpg_echo
841 shopt -u xpg_echo
842 p=1
843 noflag=1
844 set=1
845 ## END
846
847 ## N-I dash/mksh STDOUT:
848 ## END
849
850 #### no-ops not shown by shopt -p
851
852 shopt -p | grep xpg
853 echo --
854 ## STDOUT:
855 --
856 ## END
857 ## OK bash STDOUT:
858 shopt -u xpg_echo
859 --
860 ## END
861
862