OILS / spec / sh-options.test.sh View on Github | oilshell.org

746 lines, 396 significant
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
16set -o pipefail -o nounset
17echo $-
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
26set -efuC
27o=$-
28[[ $o == *e* ]]; echo yes
29[[ $o == *f* ]]; echo yes
30[[ $o == *u* ]]; echo yes
31[[ $o == *C* ]]; echo yes
32## STDOUT:
33yes
34yes
35yes
36yes
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:
45FALSE
46TRUE
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:
62foo *.nonexistent bar
63foo 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.
71set -o errexit
72set -o STRICT || true # unknown option
73echo 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
81set -o errexit a b c
82echo "$@"
83false
84echo done
85## stdout: a b c
86## status: 1
87
88#### set -o vi/emacs
89set -o vi
90echo $?
91set -o emacs
92echo $?
93## STDOUT:
940
950
96## END
97
98#### vi and emacs are mutually exclusive
99show() {
100 shopt -o -p | egrep 'emacs$|vi$'
101 echo ___
102};
103show
104
105set -o emacs
106show
107
108set -o vi
109show
110
111## STDOUT:
112set +o emacs
113set +o vi
114___
115set -o emacs
116set +o vi
117___
118set +o emacs
119set -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
129case $SH in (dash) exit ;; esac
130case $SH in (bash|*osh) flag='--rcfile /dev/null' ;; esac
131
132code='test -o emacs; echo $?; test -o vi; echo $?'
133
134echo non-interactive
135$SH $flag -c "$code"
136
137echo interactive
138$SH $flag -i -c "$code"
139
140## STDOUT:
141non-interactive
1421
1431
144interactive
1450
1461
147## END
148## OK mksh STDOUT:
149non-interactive
1500
1511
152interactive
1530
1541
155## END
156## N-I dash stdout-json: ""
157
158#### nounset
159echo "[$unset]"
160set -o nounset
161echo "[$unset]"
162echo end # never reached
163## stdout: []
164## status: 1
165## OK dash status: 2
166
167#### -u is nounset
168echo "[$unset]"
169set -u
170echo "[$unset]"
171echo end # never reached
172## stdout: []
173## status: 1
174## OK dash status: 2
175
176#### nounset with "$@"
177set a b c
178set -u # shouldn't touch argv
179echo "$@"
180## stdout: a b c
181
182#### set -u -- clears argv
183set a b c
184set -u -- # shouldn't touch argv
185echo "$@"
186## stdout:
187
188#### set -u -- x y z
189set a b c
190set -u -- x y z
191echo "$@"
192## stdout: x y z
193
194#### reset option with long flag
195set -o errexit
196set +o errexit
197echo "[$unset]"
198## stdout: []
199## status: 0
200
201#### reset option with short flag
202set -u
203set +u
204echo "[$unset]"
205## stdout: []
206## status: 0
207
208#### set -eu (flag parsing)
209set -eu
210echo "[$unset]"
211echo 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!
218echo 1
219set -n
220echo 2
221set +n
222echo 3
223# osh doesn't work because it only checks -n in bin/oil.py?
224## STDOUT:
2251
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; }
233echo $?
234set -o pipefail
235{ sleep 0.01; exit 9; } | { sleep 0.02; exit 2; } | { sleep 0.03; }
236echo $?
237## STDOUT:
2380
2392
240## END
241## status: 0
242## N-I dash STDOUT:
2430
244## END
245## N-I dash status: 2
246
247#### shopt -p -o prints 'set' options
248case $SH in dash|mksh) exit ;; esac
249
250shopt -po nounset
251set -o nounset
252shopt -po nounset
253
254echo --
255
256shopt -po | egrep -o 'errexit|noglob|nounset'
257
258## STDOUT:
259set +o nounset
260set -o nounset
261--
262errexit
263noglob
264nounset
265## END
266## N-I dash/mksh STDOUT:
267## END
268
269#### shopt -o prints 'set' options
270case $SH in dash|mksh) exit ;; esac
271
272shopt -o | egrep -o 'errexit|noglob|nounset'
273echo --
274## STDOUT:
275errexit
276noglob
277nounset
278--
279## END
280## N-I dash/mksh STDOUT:
281## END
282
283#### shopt -p prints 'shopt' options
284shopt -p nullglob
285shopt -s nullglob
286shopt -p nullglob
287## STDOUT:
288shopt -u nullglob
289shopt -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
295cd $TMP
296
297# print specific options. OSH does it in a different format.
298shopt nullglob failglob > one.txt
299wc -l one.txt
300grep -o nullglob one.txt
301grep -o failglob one.txt
302
303# print all options
304shopt | grep nullglob | wc -l
305## STDOUT:
3062 one.txt
307nullglob
308failglob
3091
310## END
311## N-I dash/mksh STDOUT:
3120 one.txt
3130
314## END
315
316#### noclobber off
317set -o errexit
318echo foo > $TMP/can-clobber
319set +C
320echo foo > $TMP/can-clobber
321set +o noclobber
322echo foo > $TMP/can-clobber
323cat $TMP/can-clobber
324## stdout: foo
325
326#### noclobber on
327# Not implemented yet.
328rm $TMP/no-clobber
329set -C
330echo foo > $TMP/no-clobber
331echo $?
332echo foo > $TMP/no-clobber
333echo $?
334## stdout-json: "0\n1\n"
335## OK dash stdout-json: "0\n2\n"
336
337#### SHELLOPTS is updated when options are changed
338echo $SHELLOPTS | grep -q xtrace
339echo $?
340set -x
341echo $SHELLOPTS | grep -q xtrace
342echo $?
343set +x
344echo $SHELLOPTS | grep -q xtrace
345echo $?
346## STDOUT:
3471
3480
3491
350## END
351## N-I dash/mksh STDOUT:
3521
3531
3541
355## END
356
357#### SHELLOPTS is readonly
358SHELLOPTS=x
359echo 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
373echo shellopts ${SHELLOPTS:?} > /dev/null
374echo 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 - -
384set a b
385echo "$@"
386set - a b
387echo "$@"
388set -- a b
389echo "$@"
390set - -
391echo "$@"
392set - +
393echo "$@"
394set + -
395echo "$@"
396set -- --
397echo "$@"
398
399# note: zsh is different, and yash is totally different
400## STDOUT:
401a b
402a b
403a b
404-
405+
406+
407--
408## END
409## OK osh/yash STDOUT:
410a b
411- a b
412a b
413- -
414- +
415+ -
416--
417## END
418## BUG mksh STDOUT:
419a b
420a b
421a b
422-
423+
424-
425--
426## END
427## BUG zsh STDOUT:
428a b
429a b
430a b
431
432+
433
434--
435## END
436
437#### set -o lists options
438# NOTE: osh doesn't use the same format yet.
439set -o | grep -o noexec
440## STDOUT:
441noexec
442## END
443
444#### set without args lists variables
445__GLOBAL=g
446f() {
447 local __mylocal=L
448 local __OTHERLOCAL=L
449 __GLOBAL=mutated
450 set | grep '^__'
451}
452g() {
453 local __var_in_parent_scope=D
454 f
455}
456g
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
487varfile=$TMP/vars-$(basename $SH).txt
488
489set | grep '^_' > "$varfile"
490
491# Unset variables
492unset _space _whitespace _sq _backslash_dq _unicode
493echo [ $_space $_whitespace $_sq $_backslash_dq $_unicode ]
494
495# Restore them
496
497. $varfile
498echo "Code saved to $varfile" 1>&2 # for debugging
499
500test "$_space" = '[ ]' && echo OK
501test "$_whitespace" = $'[\t\r\n]' && echo OK
502test "$_sq" = "'single quotes'" && echo OK
503test "$_backslash_dq" = "\\ \"" && echo OK
504test "$_unicode" = $'[\u03bc]' && echo OK
505
506## STDOUT:
507[ ]
508OK
509OK
510OK
511OK
512OK
513## END
514
515#### set without args and array variables (not in OSH)
516declare -a __array
517__array=(1 2 '3 4')
518set | 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)
532typeset -A __assoc
533__assoc['k e y']='v a l'
534__assoc[a]=b
535set | 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
547shopt -q nullglob
548echo nullglob=$?
549
550# set it
551shopt -s nullglob
552
553shopt -q nullglob
554echo nullglob=$?
555
556shopt -q nullglob failglob
557echo nullglob,failglob=$?
558
559# set it
560shopt -s failglob
561shopt -q nullglob failglob
562echo nullglob,failglob=$?
563
564## STDOUT:
565nullglob=1
566nullglob=0
567nullglob,failglob=1
568nullglob,failglob=0
569## END
570## N-I dash/mksh STDOUT:
571nullglob=127
572nullglob=127
573nullglob,failglob=127
574nullglob,failglob=127
575## END
576
577#### shopt -q invalid
578shopt -q invalidZZ
579echo invalidZZ=$?
580## STDOUT:
581invalidZZ=2
582## END
583## OK bash STDOUT:
584invalidZZ=1
585## END
586## N-I dash/mksh STDOUT:
587invalidZZ=127
588## END
589
590#### shopt -s strict:all
591n=2
592
593show-strict() {
594 shopt -p | grep 'strict_' | head -n $n
595 echo -
596}
597
598show-strict
599shopt -s strict:all
600show-strict
601shopt -u strict_arith
602show-strict
603## STDOUT:
604shopt -u strict_argv
605shopt -u strict_arith
606-
607shopt -s strict_argv
608shopt -s strict_arith
609-
610shopt -s strict_argv
611shopt -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
625set -o errexit
626
627shopt -p nullglob || true # bash returns 1 here? Like -q.
628
629# This should set nullglob, and return 1, which can be ignored
630shopt -s nullglob strict_OPTION_NOT_YET_IMPLEMENTED 2>/dev/null || true
631echo status=$?
632
633shopt -p nullglob || true
634
635## STDOUT:
636shopt -u nullglob
637status=0
638shopt -s nullglob
639## END
640## N-I dash/mksh STDOUT:
641status=0
642## END
643## N-I dash/mksh status: 0
644
645#### shopt -p validates option names
646shopt -p nullglob invalid failglob
647echo status=$?
648# same thing as -p, slightly different format in bash
649shopt nullglob invalid failglob > $TMP/out.txt
650status=$?
651sed --regexp-extended 's/\s+/ /' $TMP/out.txt # make it easier to assert
652echo status=$status
653## STDOUT:
654status=2
655status=2
656## END
657## OK bash STDOUT:
658shopt -u nullglob
659shopt -u failglob
660status=1
661nullglob off
662failglob off
663status=1
664## END
665## N-I dash/mksh STDOUT:
666status=127
667status=127
668## END
669
670#### shopt -p -o validates option names
671shopt -p -o errexit invalid nounset
672echo status=$?
673## STDOUT:
674set +o errexit
675status=2
676## END
677## OK bash STDOUT:
678set +o errexit
679set +o nounset
680status=1
681## END
682## N-I dash/mksh STDOUT:
683status=127
684## END
685
686#### stubbed out bash options
687for name in foo autocd cdable_vars checkwinsize; do
688 shopt -s $name
689 echo $?
690done
691## STDOUT:
6922
6930
6940
6950
696## END
697## OK bash STDOUT:
6981
6990
7000
7010
702## END
703## OK dash/mksh STDOUT:
704127
705127
706127
707127
708## END
709
710#### shopt -s nounset works in Oil, not in bash
711case $SH in
712 *dash|*mksh)
713 echo N-I
714 exit
715 ;;
716esac
717shopt -s nounset
718echo status=$?
719
720# get rid of extra space in bash output
721set -o | grep nounset | sed 's/[ \t]\+/ /g'
722
723## STDOUT:
724status=0
725set -o nounset
726## END
727## OK bash STDOUT:
728status=1
729nounset off
730# END
731## N-I dash/mksh STDOUT:
732N-I
733## END
734
735#### no-ops not in shopt -p output
736shopt -p | grep xpg
737echo --
738## STDOUT:
739--
740## END
741## OK bash STDOUT:
742shopt -u xpg_echo
743--
744## END
745
746