1 #!/usr/bin/env bash
2 #
3 # Test set flags, sh flags.
4
5 #### $- with -c
6 # dash's behavior seems most sensible here?
7 $SH -o nounset -c 'echo $-'
8 ## stdout: u
9 ## OK bash stdout: huBc
10 ## OK mksh stdout: uhc
11 ## status: 0
12
13 #### $- with pipefail
14 set -o pipefail -o nounset
15 echo $-
16 ## stdout: u
17 ## status: 0
18 ## OK bash stdout: huBs
19 ## OK mksh stdout: ush
20 ## N-I dash stdout-json: ""
21 ## N-I dash status: 2
22
23 #### $- and more options
24 set -efuC
25 o=$-
26 [[ $o == *e* ]]; echo yes
27 [[ $o == *f* ]]; echo yes
28 [[ $o == *u* ]]; echo yes
29 [[ $o == *C* ]]; echo yes
30 ## STDOUT:
31 yes
32 yes
33 yes
34 yes
35 ## END
36 ## N-I dash stdout-json: ""
37 ## N-I dash status: 127
38
39 #### $- with interactive shell
40 $SH -c 'echo $-' | grep i || echo FALSE
41 $SH -i -c 'echo $-' | grep -q i && echo TRUE
42 ## STDOUT:
43 FALSE
44 TRUE
45 ## END
46 #### pass short options like sh -e
47 $SH -e -c 'false; echo status=$?'
48 ## stdout-json: ""
49 ## status: 1
50
51 #### pass long options like sh -o errexit
52 $SH -o errexit -c 'false; echo status=$?'
53 ## stdout-json: ""
54 ## status: 1
55
56 #### pass shopt options like sh -O nullglob
57 $SH +O nullglob -c 'echo foo *.nonexistent bar'
58 $SH -O nullglob -c 'echo foo *.nonexistent bar'
59 ## STDOUT:
60 foo *.nonexistent bar
61 foo bar
62 ## END
63 ## N-I dash/mksh stdout-json: ""
64 ## N-I dash status: 2
65 ## N-I mksh status: 1
66
67 #### can continue after unknown option
68 # dash and mksh make this a fatal error no matter what.
69 set -o errexit
70 set -o STRICT || true # unknown option
71 echo hello
72 ## stdout: hello
73 ## status: 0
74 ## BUG dash/mksh stdout-json: ""
75 ## BUG dash status: 2
76 ## BUG mksh status: 1
77
78 #### set with both options and argv
79 set -o errexit a b c
80 echo "$@"
81 false
82 echo done
83 ## stdout: a b c
84 ## status: 1
85
86 #### set -o vi/emacs
87 set -o vi
88 echo $?
89 set -o emacs
90 echo $?
91 ## STDOUT:
92 0
93 0
94 ## END
95
96 #### vi and emacs are mutually exclusive
97 show() {
98 shopt -o -p | egrep 'emacs$|vi$'
99 echo ___
100 };
101 show
102
103 set -o emacs
104 show
105
106 set -o vi
107 show
108
109 ## STDOUT:
110 set +o emacs
111 set +o vi
112 ___
113 set -o emacs
114 set +o vi
115 ___
116 set +o emacs
117 set -o vi
118 ___
119 ## END
120 ## N-I dash/mksh STDOUT:
121 ___
122 ___
123 ___
124 ## END
125
126 #### interactive shell starts with emacs mode on
127 case $SH in (dash) exit ;; esac
128 case $SH in (bash|*osh) flag='--rcfile /dev/null' ;; esac
129
130 code='test -o emacs; echo $?; test -o vi; echo $?'
131
132 echo non-interactive
133 $SH $flag -c "$code"
134
135 echo interactive
136 $SH $flag -i -c "$code"
137
138 ## STDOUT:
139 non-interactive
140 1
141 1
142 interactive
143 0
144 1
145 ## END
146 ## OK mksh STDOUT:
147 non-interactive
148 0
149 1
150 interactive
151 0
152 1
153 ## END
154 ## N-I dash stdout-json: ""
155
156 #### nounset
157 echo "[$unset]"
158 set -o nounset
159 echo "[$unset]"
160 echo end # never reached
161 ## stdout: []
162 ## status: 1
163 ## OK dash status: 2
164
165 #### -u is nounset
166 echo "[$unset]"
167 set -u
168 echo "[$unset]"
169 echo end # never reached
170 ## stdout: []
171 ## status: 1
172 ## OK dash status: 2
173
174 #### nounset with "$@"
175 set a b c
176 set -u # shouldn't touch argv
177 echo "$@"
178 ## stdout: a b c
179
180 #### set -u -- clears argv
181 set a b c
182 set -u -- # shouldn't touch argv
183 echo "$@"
184 ## stdout:
185
186 #### set -u -- x y z
187 set a b c
188 set -u -- x y z
189 echo "$@"
190 ## stdout: x y z
191
192 #### reset option with long flag
193 set -o errexit
194 set +o errexit
195 echo "[$unset]"
196 ## stdout: []
197 ## status: 0
198
199 #### reset option with short flag
200 set -u
201 set +u
202 echo "[$unset]"
203 ## stdout: []
204 ## status: 0
205
206 #### set -eu (flag parsing)
207 set -eu
208 echo "[$unset]"
209 echo status=$?
210 ## stdout-json: ""
211 ## status: 1
212 ## OK dash status: 2
213
214 #### -n for no execution (useful with --ast-output)
215 # NOTE: set +n doesn't work because nothing is executed!
216 echo 1
217 set -n
218 echo 2
219 set +n
220 echo 3
221 # osh doesn't work because it only checks -n in bin/oil.py?
222 ## STDOUT:
223 1
224 ## END
225 ## status: 0
226
227 #### pipefail
228 # NOTE: the sleeps are because osh can fail non-deterministically because of a
229 # bug. Same problem as PIPESTATUS.
230 { sleep 0.01; exit 9; } | { sleep 0.02; exit 2; } | { sleep 0.03; }
231 echo $?
232 set -o pipefail
233 { sleep 0.01; exit 9; } | { sleep 0.02; exit 2; } | { sleep 0.03; }
234 echo $?
235 ## STDOUT:
236 0
237 2
238 ## END
239 ## status: 0
240 ## N-I dash STDOUT:
241 0
242 ## END
243 ## N-I dash status: 2
244
245 #### shopt -p -o prints 'set' options
246 shopt -po nounset
247 set -o nounset
248 shopt -po nounset
249 ## STDOUT:
250 set +o nounset
251 set -o nounset
252 ## END
253 ## N-I dash/mksh stdout-json: ""
254 ## N-I dash/mksh status: 127
255
256 #### shopt -p prints 'shopt' options
257 shopt -p nullglob
258 shopt -s nullglob
259 shopt -p nullglob
260 ## STDOUT:
261 shopt -u nullglob
262 shopt -s nullglob
263 ## END
264 ## N-I dash/mksh stdout-json: ""
265 ## N-I dash/mksh status: 127
266
267 #### shopt with no flags prints options
268 cd $TMP
269
270 # print specific options. OSH does it in a different format.
271 shopt nullglob failglob > one.txt
272 wc -l one.txt
273 grep -o nullglob one.txt
274 grep -o failglob one.txt
275
276 # print all options
277 shopt | grep nullglob | wc -l
278 ## STDOUT:
279 2 one.txt
280 nullglob
281 failglob
282 1
283 ## END
284 ## N-I dash/mksh STDOUT:
285 0 one.txt
286 0
287 ## END
288
289 #### noclobber off
290 set -o errexit
291 echo foo > $TMP/can-clobber
292 set +C
293 echo foo > $TMP/can-clobber
294 set +o noclobber
295 echo foo > $TMP/can-clobber
296 cat $TMP/can-clobber
297 ## stdout: foo
298
299 #### noclobber on
300 # Not implemented yet.
301 rm $TMP/no-clobber
302 set -C
303 echo foo > $TMP/no-clobber
304 echo $?
305 echo foo > $TMP/no-clobber
306 echo $?
307 ## stdout-json: "0\n1\n"
308 ## OK dash stdout-json: "0\n2\n"
309
310 #### SHELLOPTS is updated when options are changed
311 echo $SHELLOPTS | grep -q xtrace
312 echo $?
313 set -x
314 echo $SHELLOPTS | grep -q xtrace
315 echo $?
316 set +x
317 echo $SHELLOPTS | grep -q xtrace
318 echo $?
319 ## stdout-json: "1\n0\n1\n"
320 ## N-I dash/mksh stdout-json: "1\n1\n1\n"
321
322 #### SHELLOPTS is readonly
323 SHELLOPTS=x
324 echo status=$?
325 ## stdout: status=1
326 ## N-I dash/mksh stdout: status=0
327
328 # Setting a readonly variable in osh is a hard failure.
329 ## OK osh status: 1
330 ## OK osh stdout-json: ""
331
332 #### set - -
333 set a b
334 echo "$@"
335 set - a b
336 echo "$@"
337 set -- a b
338 echo "$@"
339 set - -
340 echo "$@"
341 set - +
342 echo "$@"
343 set + -
344 echo "$@"
345 set -- --
346 echo "$@"
347
348 # note: zsh is diffferent, and yash is totally different
349 ## STDOUT:
350 a b
351 a b
352 a b
353 -
354 +
355 +
356 --
357 ## END
358 ## OK osh/yash STDOUT:
359 a b
360 - a b
361 a b
362 - -
363 - +
364 + -
365 --
366 ## END
367 ## BUG mksh STDOUT:
368 a b
369 a b
370 a b
371 -
372 +
373 -
374 --
375 ## END
376 ## BUG zsh STDOUT:
377 a b
378 a b
379 a b
380
381 +
382
383 --
384 ## END
385
386 #### set -o lists options
387 # NOTE: osh doesn't use the same format yet.
388 set -o | grep -o noexec
389 ## STDOUT:
390 noexec
391 ## END
392
393 #### set without args lists variables
394 __GLOBAL=g
395 f() {
396 local __mylocal=L
397 local __OTHERLOCAL=L
398 __GLOBAL=mutated
399 set | grep '^__'
400 }
401 g() {
402 local __var_in_parent_scope=D
403 f
404 }
405 g
406 ## status: 0
407 ## STDOUT:
408 __GLOBAL=mutated
409 __OTHERLOCAL=L
410 __mylocal=L
411 __var_in_parent_scope=D
412 ## END
413 ## OK mksh STDOUT:
414 __GLOBAL=mutated
415 __var_in_parent_scope=D
416 __OTHERLOCAL=L
417 __mylocal=L
418 ## END
419 ## OK dash STDOUT:
420 __GLOBAL='mutated'
421 __OTHERLOCAL='L'
422 __mylocal='L'
423 __var_in_parent_scope='D'
424 ## END
425
426 #### 'set' and 'eval' round trip
427
428 # NOTE: not testing arrays and associative arrays!
429 _space='[ ]'
430 _whitespace=$'[\t\r\n]'
431 _sq="'single quotes'"
432 _backslash_dq="\\ \""
433 _unicode=$'[\u03bc]'
434
435 # Save the variables
436 varfile=$TMP/vars-$(basename $SH).txt
437
438 set | grep '^_' > "$varfile"
439
440 # Unset variables
441 unset _space _whitespace _sq _backslash_dq _unicode
442 echo [ $_space $_whitespace $_sq $_backslash_dq $_unicode ]
443
444 # Restore them
445
446 . $varfile
447 echo "Code saved to $varfile" 1>&2 # for debugging
448
449 test "$_space" = '[ ]' && echo OK
450 test "$_whitespace" = $'[\t\r\n]' && echo OK
451 test "$_sq" = "'single quotes'" && echo OK
452 test "$_backslash_dq" = "\\ \"" && echo OK
453 test "$_unicode" = $'[\u03bc]' && echo OK
454
455 ## STDOUT:
456 [ ]
457 OK
458 OK
459 OK
460 OK
461 OK
462 ## END
463
464 #### set without args and array variables (not in OSH)
465 declare -a __array
466 __array=(1 2 '3 4')
467 set | grep '^__'
468 ## STDOUT:
469 __array=([0]="1" [1]="2" [2]="3 4")
470 ## END
471 ## OK mksh STDOUT:
472 __array[0]=1
473 __array[1]=2
474 __array[2]='3 4'
475 ## N-I dash stdout-json: ""
476 ## N-I dash status: 2
477 ## N-I osh stdout-json: ""
478 ## N-I osh status: 1
479
480 #### set without args and assoc array variables (not in OSH)
481 typeset -A __assoc
482 __assoc['k e y']='v a l'
483 __assoc[a]=b
484 set | grep '^__'
485 ## STDOUT:
486 __assoc=(["k e y"]="v a l" [a]="b" )
487 ## END
488 ## N-I mksh stdout-json: ""
489 ## N-I mksh status: 1
490 ## N-I dash stdout-json: ""
491 ## N-I dash status: 1
492 ## N-I osh stdout-json: ""
493 ## N-I osh status: 1
494
495 #### shopt -q
496 shopt -q nullglob
497 echo nullglob=$?
498
499 # set it
500 shopt -s nullglob
501
502 shopt -q nullglob
503 echo nullglob=$?
504
505 shopt -q nullglob failglob
506 echo nullglob,failglob=$?
507
508 # set it
509 shopt -s failglob
510 shopt -q nullglob failglob
511 echo nullglob,failglob=$?
512
513 ## STDOUT:
514 nullglob=1
515 nullglob=0
516 nullglob,failglob=1
517 nullglob,failglob=0
518 ## END
519 ## N-I dash/mksh STDOUT:
520 nullglob=127
521 nullglob=127
522 nullglob,failglob=127
523 nullglob,failglob=127
524 ## END
525
526 #### shopt -q invalid
527 shopt -q invalidZZ
528 echo invalidZZ=$?
529 ## STDOUT:
530 invalidZZ=2
531 ## END
532 ## OK bash STDOUT:
533 invalidZZ=1
534 ## END
535 ## N-I dash/mksh STDOUT:
536 invalidZZ=127
537 ## END
538
539 #### shopt -s strict:all
540 n=2
541
542 show-strict() {
543 shopt -p | grep 'strict_' | head -n $n
544 echo -
545 }
546
547 show-strict
548 shopt -s strict:all
549 show-strict
550 shopt -u strict_arith
551 show-strict
552 ## STDOUT:
553 shopt -u strict_argv
554 shopt -u strict_arith
555 -
556 shopt -s strict_argv
557 shopt -s strict_arith
558 -
559 shopt -s strict_argv
560 shopt -u strict_arith
561 -
562 ## END
563 ## N-I dash status: 2
564 ## N-I dash stdout-json: ""
565 ## N-I bash/mksh STDOUT:
566 -
567 -
568 -
569 ## END
570
571 #### shopt allows for backward compatibility like bash
572
573 # doesn't have to be on, but just for testing
574 set -o errexit
575
576 shopt -p nullglob || true # bash returns 1 here? Like -q.
577
578 # This should set nullglob, and return 1, which can be ignored
579 shopt -s nullglob strict_OPTION_NOT_YET_IMPLEMENTED 2>/dev/null || true
580 echo status=$?
581
582 shopt -p nullglob || true
583
584 ## STDOUT:
585 shopt -u nullglob
586 status=0
587 shopt -s nullglob
588 ## END
589 ## N-I dash/mksh STDOUT:
590 status=0
591 ## END
592 ## N-I dash/mksh status: 0
593
594 #### shopt -p validates option names
595 shopt -p nullglob invalid failglob
596 echo status=$?
597 # same thing as -p, slightly different format in bash
598 shopt nullglob invalid failglob > $TMP/out.txt
599 status=$?
600 sed --regexp-extended 's/\s+/ /' $TMP/out.txt # make it easier to assert
601 echo status=$status
602 ## STDOUT:
603 shopt -u nullglob
604 status=2
605 shopt -u nullglob
606 status=2
607 ## END
608 ## OK bash STDOUT:
609 shopt -u nullglob
610 shopt -u failglob
611 status=1
612 nullglob off
613 failglob off
614 status=1
615 ## END
616 ## N-I dash/mksh STDOUT:
617 status=127
618 status=127
619 ## END
620
621 #### shopt -p -o validates option names
622 shopt -p -o errexit invalid nounset
623 echo status=$?
624 ## STDOUT:
625 set +o errexit
626 status=2
627 ## END
628 ## OK bash STDOUT:
629 set +o errexit
630 set +o nounset
631 status=1
632 ## END
633 ## N-I dash/mksh STDOUT:
634 status=127
635 ## END
636
637 #### stubbed out bash options
638 for name in foo autocd cdable_vars checkwinsize; do
639 shopt -s $name
640 echo $?
641 done
642 ## STDOUT:
643 2
644 0
645 0
646 0
647 ## END
648 ## OK bash STDOUT:
649 1
650 0
651 0
652 0
653 ## END
654 ## OK dash/mksh STDOUT:
655 127
656 127
657 127
658 127
659 ## END
660
661 #### shopt -s nounset doesn't work (may relax this later)
662 case $SH in
663 *dash|*mksh)
664 echo N-I
665 exit
666 ;;
667 esac
668 shopt -s nounset
669 echo status=$?
670 # get rid of extra space in bash
671 set -o | grep nounset | sed 's/[ \t]\+/ /g'
672
673 ## STDOUT:
674 status=2
675 set +o nounset
676 ## END
677 ## OK bash STDOUT:
678 status=1
679 nounset off
680 # END
681 ## N-I dash/mksh STDOUT:
682 N-I
683 ## END
684
685 #### no-ops not in shopt -p output
686 shopt -p | grep xpg
687 echo --
688 ## STDOUT:
689 --
690 ## END
691 ## OK bash STDOUT:
692 shopt -u xpg_echo
693 --
694 ## END
695
696