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