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