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

875 lines, 467 significant
1## compare_shells: bash dash mksh
2## oils_failures_allowed: 3
3## tags: interactive
4
5# Test options to set, shopt, $SH.
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
318
319echo foo > can-clobber
320echo status=$?
321set +C
322
323echo foo > can-clobber
324echo status=$?
325set +o noclobber
326
327echo foo > can-clobber
328echo status=$?
329cat can-clobber
330
331## STDOUT:
332status=0
333status=0
334status=0
335foo
336## END
337
338#### noclobber on
339
340rm -f no-clobber
341set -C
342
343echo foo > no-clobber
344echo create=$?
345
346echo overwrite > no-clobber
347echo overwrite=$?
348
349echo force >| no-clobber
350echo force=$?
351
352cat no-clobber
353
354## STDOUT:
355create=0
356overwrite=1
357force=0
358force
359## END
360## OK dash STDOUT:
361create=0
362overwrite=2
363force=0
364force
365## END
366
367#### noclobber on <>
368set -C
369echo foo >| $TMP/no-clobber
370exec 3<> $TMP/no-clobber
371read -n 1 <&3
372echo -n . >&3
373exec 3>&-
374cat $TMP/no-clobber
375## stdout-json: "f.o\n"
376## N-I dash stdout-json: ".oo\n"
377
378#### SHELLOPTS is updated when options are changed
379echo $SHELLOPTS | grep -q xtrace
380echo $?
381set -x
382echo $SHELLOPTS | grep -q xtrace
383echo $?
384set +x
385echo $SHELLOPTS | grep -q xtrace
386echo $?
387## STDOUT:
3881
3890
3901
391## END
392## N-I dash/mksh STDOUT:
3931
3941
3951
396## END
397
398#### SHELLOPTS is readonly
399SHELLOPTS=x
400echo status=$?
401## stdout: status=1
402## N-I dash/mksh stdout: status=0
403
404# Setting a readonly variable in osh is a hard failure.
405## OK osh status: 1
406## OK osh stdout-json: ""
407
408#### SHELLOPTS and BASHOPTS are set
409
410# 2024-06 - tickled by Samuel testing Gentoo
411
412# bash: bracexpand:hashall etc.
413
414echo shellopts ${SHELLOPTS:?} > /dev/null
415echo bashopts ${BASHOPTS:?} > /dev/null
416
417## STDOUT:
418## END
419
420## N-I dash status: 2
421## N-I mksh status: 1
422
423
424#### set - -
425set a b
426echo "$@"
427set - a b
428echo "$@"
429set -- a b
430echo "$@"
431set - -
432echo "$@"
433set - +
434echo "$@"
435set + -
436echo "$@"
437set -- --
438echo "$@"
439
440# note: zsh is different, and yash is totally different
441## STDOUT:
442a b
443a b
444a b
445-
446+
447+
448--
449## END
450## OK osh/yash STDOUT:
451a b
452- a b
453a b
454- -
455- +
456+ -
457--
458## END
459## BUG mksh STDOUT:
460a b
461a b
462a b
463-
464+
465-
466--
467## END
468## BUG zsh STDOUT:
469a b
470a b
471a b
472
473+
474
475--
476## END
477
478#### set -o lists options
479# NOTE: osh doesn't use the same format yet.
480set -o | grep -o noexec
481## STDOUT:
482noexec
483## END
484
485#### set without args lists variables
486__GLOBAL=g
487f() {
488 local __mylocal=L
489 local __OTHERLOCAL=L
490 __GLOBAL=mutated
491 set | grep '^__'
492}
493g() {
494 local __var_in_parent_scope=D
495 f
496}
497g
498## status: 0
499## STDOUT:
500__GLOBAL=mutated
501__OTHERLOCAL=L
502__mylocal=L
503__var_in_parent_scope=D
504## END
505## OK mksh STDOUT:
506__GLOBAL=mutated
507__var_in_parent_scope=D
508__OTHERLOCAL=L
509__mylocal=L
510## END
511## OK dash STDOUT:
512__GLOBAL='mutated'
513__OTHERLOCAL='L'
514__mylocal='L'
515__var_in_parent_scope='D'
516## END
517
518#### 'set' and 'eval' round trip
519
520# NOTE: not testing arrays and associative arrays!
521_space='[ ]'
522_whitespace=$'[\t\r\n]'
523_sq="'single quotes'"
524_backslash_dq="\\ \""
525_unicode=$'[\u03bc]'
526
527# Save the variables
528varfile=$TMP/vars-$(basename $SH).txt
529
530set | grep '^_' > "$varfile"
531
532# Unset variables
533unset _space _whitespace _sq _backslash_dq _unicode
534echo [ $_space $_whitespace $_sq $_backslash_dq $_unicode ]
535
536# Restore them
537
538. $varfile
539echo "Code saved to $varfile" 1>&2 # for debugging
540
541test "$_space" = '[ ]' && echo OK
542test "$_whitespace" = $'[\t\r\n]' && echo OK
543test "$_sq" = "'single quotes'" && echo OK
544test "$_backslash_dq" = "\\ \"" && echo OK
545test "$_unicode" = $'[\u03bc]' && echo OK
546
547## STDOUT:
548[ ]
549OK
550OK
551OK
552OK
553OK
554## END
555
556#### set without args and array variables (not in OSH)
557declare -a __array
558__array=(1 2 '3 4')
559set | grep '^__'
560## STDOUT:
561__array=([0]="1" [1]="2" [2]="3 4")
562## END
563## OK mksh STDOUT:
564__array[0]=1
565__array[1]=2
566__array[2]='3 4'
567## N-I dash stdout-json: ""
568## N-I dash status: 2
569## N-I osh stdout-json: ""
570## N-I osh status: 1
571
572#### set without args and assoc array variables (not in OSH)
573typeset -A __assoc
574__assoc['k e y']='v a l'
575__assoc[a]=b
576set | grep '^__'
577## STDOUT:
578__assoc=([a]="b" ["k e y"]="v a l" )
579## END
580## N-I mksh stdout-json: ""
581## N-I mksh status: 1
582## N-I dash stdout-json: ""
583## N-I dash status: 1
584## N-I osh stdout-json: ""
585## N-I osh status: 1
586
587#### shopt -q
588shopt -q nullglob
589echo nullglob=$?
590
591# set it
592shopt -s nullglob
593
594shopt -q nullglob
595echo nullglob=$?
596
597shopt -q nullglob failglob
598echo nullglob,failglob=$?
599
600# set it
601shopt -s failglob
602shopt -q nullglob failglob
603echo nullglob,failglob=$?
604
605## STDOUT:
606nullglob=1
607nullglob=0
608nullglob,failglob=1
609nullglob,failglob=0
610## END
611## N-I dash/mksh STDOUT:
612nullglob=127
613nullglob=127
614nullglob,failglob=127
615nullglob,failglob=127
616## END
617
618#### shopt -q invalid
619shopt -q invalidZZ
620echo invalidZZ=$?
621## STDOUT:
622invalidZZ=2
623## END
624## OK bash STDOUT:
625invalidZZ=1
626## END
627## N-I dash/mksh STDOUT:
628invalidZZ=127
629## END
630
631#### shopt -s strict:all
632n=2
633
634show-strict() {
635 shopt -p | grep 'strict_' | head -n $n
636 echo -
637}
638
639show-strict
640shopt -s strict:all
641show-strict
642shopt -u strict_arith
643show-strict
644## STDOUT:
645shopt -u strict_argv
646shopt -u strict_arith
647-
648shopt -s strict_argv
649shopt -s strict_arith
650-
651shopt -s strict_argv
652shopt -u strict_arith
653-
654## END
655## N-I dash status: 2
656## N-I dash stdout-json: ""
657## N-I bash/mksh STDOUT:
658-
659-
660-
661## END
662
663#### shopt allows for backward compatibility like bash
664
665# doesn't have to be on, but just for testing
666set -o errexit
667
668shopt -p nullglob || true # bash returns 1 here? Like -q.
669
670# This should set nullglob, and return 1, which can be ignored
671shopt -s nullglob strict_OPTION_NOT_YET_IMPLEMENTED 2>/dev/null || true
672echo status=$?
673
674shopt -p nullglob || true
675
676## STDOUT:
677shopt -u nullglob
678status=0
679shopt -s nullglob
680## END
681## N-I dash/mksh STDOUT:
682status=0
683## END
684## N-I dash/mksh status: 0
685
686#### shopt -p validates option names
687shopt -p nullglob invalid failglob
688echo status=$?
689# same thing as -p, slightly different format in bash
690shopt nullglob invalid failglob > $TMP/out.txt
691status=$?
692sed --regexp-extended 's/\s+/ /' $TMP/out.txt # make it easier to assert
693echo status=$status
694## STDOUT:
695status=2
696status=2
697## END
698## OK bash STDOUT:
699shopt -u nullglob
700shopt -u failglob
701status=1
702nullglob off
703failglob off
704status=1
705## END
706## N-I dash/mksh STDOUT:
707status=127
708status=127
709## END
710
711#### shopt -p -o validates option names
712shopt -p -o errexit invalid nounset
713echo status=$?
714## STDOUT:
715set +o errexit
716status=2
717## END
718## OK bash STDOUT:
719set +o errexit
720set +o nounset
721status=1
722## END
723## N-I dash/mksh STDOUT:
724status=127
725## END
726
727#### stubbed out bash options
728for name in foo autocd cdable_vars checkwinsize; do
729 shopt -s $name
730 echo $?
731done
732## STDOUT:
7332
7340
7350
7360
737## END
738## OK bash STDOUT:
7391
7400
7410
7420
743## END
744## OK dash/mksh STDOUT:
745127
746127
747127
748127
749## END
750
751#### shopt -s nounset works in YSH, not in bash
752case $SH in
753 *dash|*mksh)
754 echo N-I
755 exit
756 ;;
757esac
758shopt -s nounset
759echo status=$?
760
761# get rid of extra space in bash output
762set -o | grep nounset | sed 's/[ \t]\+/ /g'
763
764## STDOUT:
765status=0
766set -o nounset
767## END
768## OK bash STDOUT:
769status=1
770nounset off
771# END
772## N-I dash/mksh STDOUT:
773N-I
774## END
775
776#### Unimplemented options - print, query, set, unset
777case $SH in dash|mksh) exit ;; esac
778
779opt_name=xpg_echo
780
781shopt -p xpg_echo
782shopt -q xpg_echo; echo q=$?
783
784shopt -s xpg_echo
785shopt -p xpg_echo
786
787shopt -u xpg_echo
788shopt -p xpg_echo
789echo p=$? # weird, bash also returns a status
790
791shopt xpg_echo >/dev/null
792echo noflag=$?
793
794shopt -o errexit >/dev/null
795echo set=$?
796
797## STDOUT:
798TODO
799## END
800
801## OK bash STDOUT:
802shopt -u xpg_echo
803q=1
804shopt -s xpg_echo
805shopt -u xpg_echo
806p=1
807noflag=1
808set=1
809## END
810
811## N-I dash/mksh STDOUT:
812## END
813
814#### Unimplemented options - OSH shopt -s ignore_shopt_not_impl
815case $SH in dash|mksh) exit ;; esac
816
817shopt -s ignore_shopt_not_impl
818
819opt_name=xpg_echo
820
821shopt -p xpg_echo
822shopt -q xpg_echo; echo q=$?
823
824shopt -s xpg_echo
825shopt -p xpg_echo
826
827shopt -u xpg_echo
828shopt -p xpg_echo
829echo p=$? # weird, bash also returns a status
830
831shopt xpg_echo >/dev/null
832echo noflag=$?
833
834shopt -o errexit >/dev/null
835echo set=$?
836
837## STDOUT:
838shopt -u xpg_echo
839q=1
840shopt -s xpg_echo
841shopt -u xpg_echo
842p=1
843noflag=1
844set=1
845## END
846
847## N-I dash/mksh STDOUT:
848## END
849
850#### shopt -p exit code (regression)
851case $SH in dash|mksh) exit ;; esac
852
853shopt -p > /dev/null
854echo status=$?
855
856## STDOUT:
857status=0
858## END
859
860## N-I dash/mksh STDOUT:
861## END
862
863#### no-ops not shown by shopt -p
864
865shopt -p | grep xpg
866echo --
867## STDOUT:
868--
869## END
870## OK bash STDOUT:
871shopt -u xpg_echo
872--
873## END
874
875