1 # Test set flags, sh flags.
2
3 ## compare_shells: bash dash mksh
4 ## oils_failures_allowed: 1
5 ## tags: interactive
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 echo foo > $TMP/can-clobber
319 set +C
320 echo foo > $TMP/can-clobber
321 set +o noclobber
322 echo foo > $TMP/can-clobber
323 cat $TMP/can-clobber
324 ## stdout: foo
325
326 #### noclobber on
327 rm $TMP/no-clobber
328 set -C
329 echo foo > $TMP/no-clobber
330 echo $?
331 echo foo > $TMP/no-clobber
332 echo $?
333 echo foo >| $TMP/no-clobber
334 echo $?
335 ## stdout-json: "0\n1\n0\n"
336 ## OK dash stdout-json: "0\n2\n0\n"
337
338 #### noclobber on <>
339 set -C
340 echo foo >| $TMP/no-clobber
341 exec 3<> $TMP/no-clobber
342 read -n 1 <&3
343 echo -n . >&3
344 exec 3>&-
345 cat $TMP/no-clobber
346 ## stdout-json: "f.o\n"
347 ## N-I dash stdout-json: ".oo\n"
348
349 #### SHELLOPTS is updated when options are changed
350 echo $SHELLOPTS | grep -q xtrace
351 echo $?
352 set -x
353 echo $SHELLOPTS | grep -q xtrace
354 echo $?
355 set +x
356 echo $SHELLOPTS | grep -q xtrace
357 echo $?
358 ## STDOUT:
359 1
360 0
361 1
362 ## END
363 ## N-I dash/mksh STDOUT:
364 1
365 1
366 1
367 ## END
368
369 #### SHELLOPTS is readonly
370 SHELLOPTS=x
371 echo status=$?
372 ## stdout: status=1
373 ## N-I dash/mksh stdout: status=0
374
375 # Setting a readonly variable in osh is a hard failure.
376 ## OK osh status: 1
377 ## OK osh stdout-json: ""
378
379 #### SHELLOPTS and BASHOPTS are set
380
381 # 2024-06 - tickled by Samuel testing Gentoo
382
383 # bash: bracexpand:hashall etc.
384
385 echo shellopts ${SHELLOPTS:?} > /dev/null
386 echo bashopts ${BASHOPTS:?} > /dev/null
387
388 ## STDOUT:
389 ## END
390
391 ## N-I dash status: 2
392 ## N-I mksh status: 1
393
394
395 #### set - -
396 set a b
397 echo "$@"
398 set - a b
399 echo "$@"
400 set -- a b
401 echo "$@"
402 set - -
403 echo "$@"
404 set - +
405 echo "$@"
406 set + -
407 echo "$@"
408 set -- --
409 echo "$@"
410
411 # note: zsh is different, and yash is totally different
412 ## STDOUT:
413 a b
414 a b
415 a b
416 -
417 +
418 +
419 --
420 ## END
421 ## OK osh/yash STDOUT:
422 a b
423 - a b
424 a b
425 - -
426 - +
427 + -
428 --
429 ## END
430 ## BUG mksh STDOUT:
431 a b
432 a b
433 a b
434 -
435 +
436 -
437 --
438 ## END
439 ## BUG zsh STDOUT:
440 a b
441 a b
442 a b
443
444 +
445
446 --
447 ## END
448
449 #### set -o lists options
450 # NOTE: osh doesn't use the same format yet.
451 set -o | grep -o noexec
452 ## STDOUT:
453 noexec
454 ## END
455
456 #### set without args lists variables
457 __GLOBAL=g
458 f() {
459 local __mylocal=L
460 local __OTHERLOCAL=L
461 __GLOBAL=mutated
462 set | grep '^__'
463 }
464 g() {
465 local __var_in_parent_scope=D
466 f
467 }
468 g
469 ## status: 0
470 ## STDOUT:
471 __GLOBAL=mutated
472 __OTHERLOCAL=L
473 __mylocal=L
474 __var_in_parent_scope=D
475 ## END
476 ## OK mksh STDOUT:
477 __GLOBAL=mutated
478 __var_in_parent_scope=D
479 __OTHERLOCAL=L
480 __mylocal=L
481 ## END
482 ## OK dash STDOUT:
483 __GLOBAL='mutated'
484 __OTHERLOCAL='L'
485 __mylocal='L'
486 __var_in_parent_scope='D'
487 ## END
488
489 #### 'set' and 'eval' round trip
490
491 # NOTE: not testing arrays and associative arrays!
492 _space='[ ]'
493 _whitespace=$'[\t\r\n]'
494 _sq="'single quotes'"
495 _backslash_dq="\\ \""
496 _unicode=$'[\u03bc]'
497
498 # Save the variables
499 varfile=$TMP/vars-$(basename $SH).txt
500
501 set | grep '^_' > "$varfile"
502
503 # Unset variables
504 unset _space _whitespace _sq _backslash_dq _unicode
505 echo [ $_space $_whitespace $_sq $_backslash_dq $_unicode ]
506
507 # Restore them
508
509 . $varfile
510 echo "Code saved to $varfile" 1>&2 # for debugging
511
512 test "$_space" = '[ ]' && echo OK
513 test "$_whitespace" = $'[\t\r\n]' && echo OK
514 test "$_sq" = "'single quotes'" && echo OK
515 test "$_backslash_dq" = "\\ \"" && echo OK
516 test "$_unicode" = $'[\u03bc]' && echo OK
517
518 ## STDOUT:
519 [ ]
520 OK
521 OK
522 OK
523 OK
524 OK
525 ## END
526
527 #### set without args and array variables (not in OSH)
528 declare -a __array
529 __array=(1 2 '3 4')
530 set | grep '^__'
531 ## STDOUT:
532 __array=([0]="1" [1]="2" [2]="3 4")
533 ## END
534 ## OK mksh STDOUT:
535 __array[0]=1
536 __array[1]=2
537 __array[2]='3 4'
538 ## N-I dash stdout-json: ""
539 ## N-I dash status: 2
540 ## N-I osh stdout-json: ""
541 ## N-I osh status: 1
542
543 #### set without args and assoc array variables (not in OSH)
544 typeset -A __assoc
545 __assoc['k e y']='v a l'
546 __assoc[a]=b
547 set | grep '^__'
548 ## STDOUT:
549 __assoc=([a]="b" ["k e y"]="v a l" )
550 ## END
551 ## N-I mksh stdout-json: ""
552 ## N-I mksh status: 1
553 ## N-I dash stdout-json: ""
554 ## N-I dash status: 1
555 ## N-I osh stdout-json: ""
556 ## N-I osh status: 1
557
558 #### shopt -q
559 shopt -q nullglob
560 echo nullglob=$?
561
562 # set it
563 shopt -s nullglob
564
565 shopt -q nullglob
566 echo nullglob=$?
567
568 shopt -q nullglob failglob
569 echo nullglob,failglob=$?
570
571 # set it
572 shopt -s failglob
573 shopt -q nullglob failglob
574 echo nullglob,failglob=$?
575
576 ## STDOUT:
577 nullglob=1
578 nullglob=0
579 nullglob,failglob=1
580 nullglob,failglob=0
581 ## END
582 ## N-I dash/mksh STDOUT:
583 nullglob=127
584 nullglob=127
585 nullglob,failglob=127
586 nullglob,failglob=127
587 ## END
588
589 #### shopt -q invalid
590 shopt -q invalidZZ
591 echo invalidZZ=$?
592 ## STDOUT:
593 invalidZZ=2
594 ## END
595 ## OK bash STDOUT:
596 invalidZZ=1
597 ## END
598 ## N-I dash/mksh STDOUT:
599 invalidZZ=127
600 ## END
601
602 #### shopt -s strict:all
603 n=2
604
605 show-strict() {
606 shopt -p | grep 'strict_' | head -n $n
607 echo -
608 }
609
610 show-strict
611 shopt -s strict:all
612 show-strict
613 shopt -u strict_arith
614 show-strict
615 ## STDOUT:
616 shopt -u strict_argv
617 shopt -u strict_arith
618 -
619 shopt -s strict_argv
620 shopt -s strict_arith
621 -
622 shopt -s strict_argv
623 shopt -u strict_arith
624 -
625 ## END
626 ## N-I dash status: 2
627 ## N-I dash stdout-json: ""
628 ## N-I bash/mksh STDOUT:
629 -
630 -
631 -
632 ## END
633
634 #### shopt allows for backward compatibility like bash
635
636 # doesn't have to be on, but just for testing
637 set -o errexit
638
639 shopt -p nullglob || true # bash returns 1 here? Like -q.
640
641 # This should set nullglob, and return 1, which can be ignored
642 shopt -s nullglob strict_OPTION_NOT_YET_IMPLEMENTED 2>/dev/null || true
643 echo status=$?
644
645 shopt -p nullglob || true
646
647 ## STDOUT:
648 shopt -u nullglob
649 status=0
650 shopt -s nullglob
651 ## END
652 ## N-I dash/mksh STDOUT:
653 status=0
654 ## END
655 ## N-I dash/mksh status: 0
656
657 #### shopt -p validates option names
658 shopt -p nullglob invalid failglob
659 echo status=$?
660 # same thing as -p, slightly different format in bash
661 shopt nullglob invalid failglob > $TMP/out.txt
662 status=$?
663 sed --regexp-extended 's/\s+/ /' $TMP/out.txt # make it easier to assert
664 echo status=$status
665 ## STDOUT:
666 status=2
667 status=2
668 ## END
669 ## OK bash STDOUT:
670 shopt -u nullglob
671 shopt -u failglob
672 status=1
673 nullglob off
674 failglob off
675 status=1
676 ## END
677 ## N-I dash/mksh STDOUT:
678 status=127
679 status=127
680 ## END
681
682 #### shopt -p -o validates option names
683 shopt -p -o errexit invalid nounset
684 echo status=$?
685 ## STDOUT:
686 set +o errexit
687 status=2
688 ## END
689 ## OK bash STDOUT:
690 set +o errexit
691 set +o nounset
692 status=1
693 ## END
694 ## N-I dash/mksh STDOUT:
695 status=127
696 ## END
697
698 #### stubbed out bash options
699 for name in foo autocd cdable_vars checkwinsize; do
700 shopt -s $name
701 echo $?
702 done
703 ## STDOUT:
704 2
705 0
706 0
707 0
708 ## END
709 ## OK bash STDOUT:
710 1
711 0
712 0
713 0
714 ## END
715 ## OK dash/mksh STDOUT:
716 127
717 127
718 127
719 127
720 ## END
721
722 #### shopt -s nounset works in Oil, not in bash
723 case $SH in
724 *dash|*mksh)
725 echo N-I
726 exit
727 ;;
728 esac
729 shopt -s nounset
730 echo status=$?
731
732 # get rid of extra space in bash output
733 set -o | grep nounset | sed 's/[ \t]\+/ /g'
734
735 ## STDOUT:
736 status=0
737 set -o nounset
738 ## END
739 ## OK bash STDOUT:
740 status=1
741 nounset off
742 # END
743 ## N-I dash/mksh STDOUT:
744 N-I
745 ## END
746
747 #### no-ops not in shopt -p output
748 shopt -p | grep xpg
749 echo --
750 ## STDOUT:
751 --
752 ## END
753 ## OK bash STDOUT:
754 shopt -u xpg_echo
755 --
756 ## END
757
758