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 (not in OSH)
515 declare -a __array
516 __array=(1 2 '3 4')
517 set | grep '^__'
518 ## STDOUT:
519 __array=([0]="1" [1]="2" [2]="3 4")
520 ## END
521 ## OK mksh STDOUT:
522 __array[0]=1
523 __array[1]=2
524 __array[2]='3 4'
525 ## N-I dash stdout-json: ""
526 ## N-I dash status: 2
527 ## N-I osh stdout-json: ""
528 ## N-I osh status: 1
529
530 #### set without args and assoc array variables (not in OSH)
531 typeset -A __assoc
532 __assoc['k e y']='v a l'
533 __assoc[a]=b
534 set | grep '^__'
535 ## STDOUT:
536 __assoc=([a]="b" ["k e y"]="v a l" )
537 ## END
538 ## N-I mksh stdout-json: ""
539 ## N-I mksh status: 1
540 ## N-I dash stdout-json: ""
541 ## N-I dash status: 1
542 ## N-I osh stdout-json: ""
543 ## N-I osh status: 1
544
545 #### shopt -q
546 shopt -q nullglob
547 echo nullglob=$?
548
549 # set it
550 shopt -s nullglob
551
552 shopt -q nullglob
553 echo nullglob=$?
554
555 shopt -q nullglob failglob
556 echo nullglob,failglob=$?
557
558 # set it
559 shopt -s failglob
560 shopt -q nullglob failglob
561 echo nullglob,failglob=$?
562
563 ## STDOUT:
564 nullglob=1
565 nullglob=0
566 nullglob,failglob=1
567 nullglob,failglob=0
568 ## END
569 ## N-I dash/mksh STDOUT:
570 nullglob=127
571 nullglob=127
572 nullglob,failglob=127
573 nullglob,failglob=127
574 ## END
575
576 #### shopt -q invalid
577 shopt -q invalidZZ
578 echo invalidZZ=$?
579 ## STDOUT:
580 invalidZZ=2
581 ## END
582 ## OK bash STDOUT:
583 invalidZZ=1
584 ## END
585 ## N-I dash/mksh STDOUT:
586 invalidZZ=127
587 ## END
588
589 #### shopt -s strict:all
590 n=2
591
592 show-strict() {
593 shopt -p | grep 'strict_' | head -n $n
594 echo -
595 }
596
597 show-strict
598 shopt -s strict:all
599 show-strict
600 shopt -u strict_arith
601 show-strict
602 ## STDOUT:
603 shopt -u strict_argv
604 shopt -u strict_arith
605 -
606 shopt -s strict_argv
607 shopt -s strict_arith
608 -
609 shopt -s strict_argv
610 shopt -u strict_arith
611 -
612 ## END
613 ## N-I dash status: 2
614 ## N-I dash stdout-json: ""
615 ## N-I bash/mksh STDOUT:
616 -
617 -
618 -
619 ## END
620
621 #### shopt allows for backward compatibility like bash
622
623 # doesn't have to be on, but just for testing
624 set -o errexit
625
626 shopt -p nullglob || true # bash returns 1 here? Like -q.
627
628 # This should set nullglob, and return 1, which can be ignored
629 shopt -s nullglob strict_OPTION_NOT_YET_IMPLEMENTED 2>/dev/null || true
630 echo status=$?
631
632 shopt -p nullglob || true
633
634 ## STDOUT:
635 shopt -u nullglob
636 status=0
637 shopt -s nullglob
638 ## END
639 ## N-I dash/mksh STDOUT:
640 status=0
641 ## END
642 ## N-I dash/mksh status: 0
643
644 #### shopt -p validates option names
645 shopt -p nullglob invalid failglob
646 echo status=$?
647 # same thing as -p, slightly different format in bash
648 shopt nullglob invalid failglob > $TMP/out.txt
649 status=$?
650 sed --regexp-extended 's/\s+/ /' $TMP/out.txt # make it easier to assert
651 echo status=$status
652 ## STDOUT:
653 status=2
654 status=2
655 ## END
656 ## OK bash STDOUT:
657 shopt -u nullglob
658 shopt -u failglob
659 status=1
660 nullglob off
661 failglob off
662 status=1
663 ## END
664 ## N-I dash/mksh STDOUT:
665 status=127
666 status=127
667 ## END
668
669 #### shopt -p -o validates option names
670 shopt -p -o errexit invalid nounset
671 echo status=$?
672 ## STDOUT:
673 set +o errexit
674 status=2
675 ## END
676 ## OK bash STDOUT:
677 set +o errexit
678 set +o nounset
679 status=1
680 ## END
681 ## N-I dash/mksh STDOUT:
682 status=127
683 ## END
684
685 #### stubbed out bash options
686 shopt -s ignore_shopt_not_impl
687 for name in foo autocd cdable_vars checkwinsize; do
688 shopt -s $name
689 echo $?
690 done
691 ## STDOUT:
692 2
693 0
694 0
695 0
696 ## END
697 ## OK bash STDOUT:
698 1
699 0
700 0
701 0
702 ## END
703 ## OK dash/mksh STDOUT:
704 127
705 127
706 127
707 127
708 ## END
709
710 #### shopt -s nounset works in YSH, not in bash
711 case $SH in
712 *dash|*mksh)
713 echo N-I
714 exit
715 ;;
716 esac
717 shopt -s nounset
718 echo status=$?
719
720 # get rid of extra space in bash output
721 set -o | grep nounset | sed 's/[ \t]\+/ /g'
722
723 ## STDOUT:
724 status=0
725 set -o nounset
726 ## END
727 ## OK bash STDOUT:
728 status=1
729 nounset off
730 # END
731 ## N-I dash/mksh STDOUT:
732 N-I
733 ## END
734
735 #### Unimplemented options - print, query, set, unset
736 case $SH in dash|mksh) exit ;; esac
737
738 opt_name=xpg_echo
739
740 shopt -p xpg_echo
741 shopt -q xpg_echo; echo q=$?
742
743 shopt -s xpg_echo
744 shopt -p xpg_echo
745
746 shopt -u xpg_echo
747 shopt -p xpg_echo
748 echo p=$? # weird, bash also returns a status
749
750 shopt xpg_echo >/dev/null
751 echo noflag=$?
752
753 shopt -o errexit >/dev/null
754 echo set=$?
755
756 ## STDOUT:
757 q=2
758 p=2
759 noflag=2
760 set=1
761 ## END
762
763 ## OK bash STDOUT:
764 shopt -u xpg_echo
765 q=1
766 shopt -s xpg_echo
767 shopt -u xpg_echo
768 p=1
769 noflag=1
770 set=1
771 ## END
772
773 ## N-I dash/mksh STDOUT:
774 ## END
775
776 #### Unimplemented options - OSH shopt -s ignore_shopt_not_impl
777 case $SH in dash|mksh) exit ;; esac
778
779 shopt -s ignore_shopt_not_impl
780
781 opt_name=xpg_echo
782
783 shopt -p xpg_echo
784 shopt -q xpg_echo; echo q=$?
785
786 shopt -s xpg_echo
787 shopt -p xpg_echo
788
789 shopt -u xpg_echo
790 shopt -p xpg_echo
791 echo p=$? # weird, bash also returns a status
792
793 shopt xpg_echo >/dev/null
794 echo noflag=$?
795
796 shopt -o errexit >/dev/null
797 echo set=$?
798
799 ## STDOUT:
800 shopt -u xpg_echo
801 q=1
802 shopt -s xpg_echo
803 shopt -u xpg_echo
804 p=1
805 noflag=1
806 set=1
807 ## END
808
809 ## N-I dash/mksh STDOUT:
810 ## END
811
812 #### shopt -p exit code (regression)
813 case $SH in dash|mksh) exit ;; esac
814
815 shopt -p > /dev/null
816 echo status=$?
817
818 ## STDOUT:
819 status=0
820 ## END
821
822 ## N-I dash/mksh STDOUT:
823 ## END
824
825 #### no-ops not shown by shopt -p
826
827 shopt -p | grep xpg
828 echo --
829 ## STDOUT:
830 --
831 ## END
832 ## OK bash STDOUT:
833 shopt -u xpg_echo
834 --
835 ## END
836
837