OILS / spec / sh-options.test.sh View on Github | oils.pub

837 lines, 455 significant
1## compare_shells: bash dash mksh
2## oils_failures_allowed: 1
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:
376f.o
377## END
378## N-I dash STDOUT:
379.oo
380## END
381
382#### set - -
383set a b
384echo "$@"
385set - a b
386echo "$@"
387set -- a b
388echo "$@"
389set - -
390echo "$@"
391set - +
392echo "$@"
393set + -
394echo "$@"
395set -- --
396echo "$@"
397
398# note: zsh is different, and yash is totally different
399## STDOUT:
400a b
401a b
402a b
403-
404+
405+
406--
407## END
408## OK osh/yash STDOUT:
409a b
410- a b
411a b
412- -
413- +
414+ -
415--
416## END
417## BUG mksh STDOUT:
418a b
419a b
420a b
421-
422+
423-
424--
425## END
426## BUG zsh STDOUT:
427a b
428a b
429a b
430
431+
432
433--
434## END
435
436#### set -o lists options
437# NOTE: osh doesn't use the same format yet.
438set -o | grep -o noexec
439## STDOUT:
440noexec
441## END
442
443#### set without args lists variables
444__GLOBAL=g
445f() {
446 local __mylocal=L
447 local __OTHERLOCAL=L
448 __GLOBAL=mutated
449 set | grep '^__'
450}
451g() {
452 local __var_in_parent_scope=D
453 f
454}
455g
456## status: 0
457## STDOUT:
458__GLOBAL=mutated
459__OTHERLOCAL=L
460__mylocal=L
461__var_in_parent_scope=D
462## END
463## OK mksh STDOUT:
464__GLOBAL=mutated
465__var_in_parent_scope=D
466__OTHERLOCAL=L
467__mylocal=L
468## END
469## OK dash STDOUT:
470__GLOBAL='mutated'
471__OTHERLOCAL='L'
472__mylocal='L'
473__var_in_parent_scope='D'
474## END
475
476#### 'set' and 'eval' round trip
477
478# NOTE: not testing arrays and associative arrays!
479_space='[ ]'
480_whitespace=$'[\t\r\n]'
481_sq="'single quotes'"
482_backslash_dq="\\ \""
483_unicode=$'[\u03bc]'
484
485# Save the variables
486varfile=$TMP/vars-$(basename $SH).txt
487
488set | grep '^_' > "$varfile"
489
490# Unset variables
491unset _space _whitespace _sq _backslash_dq _unicode
492echo [ $_space $_whitespace $_sq $_backslash_dq $_unicode ]
493
494# Restore them
495
496. $varfile
497echo "Code saved to $varfile" 1>&2 # for debugging
498
499test "$_space" = '[ ]' && echo OK
500test "$_whitespace" = $'[\t\r\n]' && echo OK
501test "$_sq" = "'single quotes'" && echo OK
502test "$_backslash_dq" = "\\ \"" && echo OK
503test "$_unicode" = $'[\u03bc]' && echo OK
504
505## STDOUT:
506[ ]
507OK
508OK
509OK
510OK
511OK
512## END
513
514#### set without args and array variables (not in OSH)
515declare -a __array
516__array=(1 2 '3 4')
517set | grep '^__'
518## STDOUT:
519__array=([0]="1" [1]="2" [2]="3 4")
520## END
521## OK mksh STDOUT:
522__array[0]=1
523__array[1]=2
524__array[2]='3 4'
525## N-I dash stdout-json: ""
526## N-I dash status: 2
527## N-I osh stdout-json: ""
528## N-I osh status: 1
529
530#### set without args and assoc array variables (not in OSH)
531typeset -A __assoc
532__assoc['k e y']='v a l'
533__assoc[a]=b
534set | grep '^__'
535## STDOUT:
536__assoc=([a]="b" ["k e y"]="v a l" )
537## END
538## N-I mksh stdout-json: ""
539## N-I mksh status: 1
540## N-I dash stdout-json: ""
541## N-I dash status: 1
542## N-I osh stdout-json: ""
543## N-I osh status: 1
544
545#### shopt -q
546shopt -q nullglob
547echo nullglob=$?
548
549# set it
550shopt -s nullglob
551
552shopt -q nullglob
553echo nullglob=$?
554
555shopt -q nullglob failglob
556echo nullglob,failglob=$?
557
558# set it
559shopt -s failglob
560shopt -q nullglob failglob
561echo nullglob,failglob=$?
562
563## STDOUT:
564nullglob=1
565nullglob=0
566nullglob,failglob=1
567nullglob,failglob=0
568## END
569## N-I dash/mksh STDOUT:
570nullglob=127
571nullglob=127
572nullglob,failglob=127
573nullglob,failglob=127
574## END
575
576#### shopt -q invalid
577shopt -q invalidZZ
578echo invalidZZ=$?
579## STDOUT:
580invalidZZ=2
581## END
582## OK bash STDOUT:
583invalidZZ=1
584## END
585## N-I dash/mksh STDOUT:
586invalidZZ=127
587## END
588
589#### shopt -s strict:all
590n=2
591
592show-strict() {
593 shopt -p | grep 'strict_' | head -n $n
594 echo -
595}
596
597show-strict
598shopt -s strict:all
599show-strict
600shopt -u strict_arith
601show-strict
602## STDOUT:
603shopt -u strict_argv
604shopt -u strict_arith
605-
606shopt -s strict_argv
607shopt -s strict_arith
608-
609shopt -s strict_argv
610shopt -u strict_arith
611-
612## END
613## N-I dash status: 2
614## N-I dash stdout-json: ""
615## N-I bash/mksh STDOUT:
616-
617-
618-
619## END
620
621#### shopt allows for backward compatibility like bash
622
623# doesn't have to be on, but just for testing
624set -o errexit
625
626shopt -p nullglob || true # bash returns 1 here? Like -q.
627
628# This should set nullglob, and return 1, which can be ignored
629shopt -s nullglob strict_OPTION_NOT_YET_IMPLEMENTED 2>/dev/null || true
630echo status=$?
631
632shopt -p nullglob || true
633
634## STDOUT:
635shopt -u nullglob
636status=0
637shopt -s nullglob
638## END
639## N-I dash/mksh STDOUT:
640status=0
641## END
642## N-I dash/mksh status: 0
643
644#### shopt -p validates option names
645shopt -p nullglob invalid failglob
646echo status=$?
647# same thing as -p, slightly different format in bash
648shopt nullglob invalid failglob > $TMP/out.txt
649status=$?
650sed --regexp-extended 's/\s+/ /' $TMP/out.txt # make it easier to assert
651echo status=$status
652## STDOUT:
653status=2
654status=2
655## END
656## OK bash STDOUT:
657shopt -u nullglob
658shopt -u failglob
659status=1
660nullglob off
661failglob off
662status=1
663## END
664## N-I dash/mksh STDOUT:
665status=127
666status=127
667## END
668
669#### shopt -p -o validates option names
670shopt -p -o errexit invalid nounset
671echo status=$?
672## STDOUT:
673set +o errexit
674status=2
675## END
676## OK bash STDOUT:
677set +o errexit
678set +o nounset
679status=1
680## END
681## N-I dash/mksh STDOUT:
682status=127
683## END
684
685#### stubbed out bash options
686shopt -s ignore_shopt_not_impl
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 YSH, 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#### Unimplemented options - print, query, set, unset
736case $SH in dash|mksh) exit ;; esac
737
738opt_name=xpg_echo
739
740shopt -p xpg_echo
741shopt -q xpg_echo; echo q=$?
742
743shopt -s xpg_echo
744shopt -p xpg_echo
745
746shopt -u xpg_echo
747shopt -p xpg_echo
748echo p=$? # weird, bash also returns a status
749
750shopt xpg_echo >/dev/null
751echo noflag=$?
752
753shopt -o errexit >/dev/null
754echo set=$?
755
756## STDOUT:
757q=2
758p=2
759noflag=2
760set=1
761## END
762
763## OK bash STDOUT:
764shopt -u xpg_echo
765q=1
766shopt -s xpg_echo
767shopt -u xpg_echo
768p=1
769noflag=1
770set=1
771## END
772
773## N-I dash/mksh STDOUT:
774## END
775
776#### Unimplemented options - OSH shopt -s ignore_shopt_not_impl
777case $SH in dash|mksh) exit ;; esac
778
779shopt -s ignore_shopt_not_impl
780
781opt_name=xpg_echo
782
783shopt -p xpg_echo
784shopt -q xpg_echo; echo q=$?
785
786shopt -s xpg_echo
787shopt -p xpg_echo
788
789shopt -u xpg_echo
790shopt -p xpg_echo
791echo p=$? # weird, bash also returns a status
792
793shopt xpg_echo >/dev/null
794echo noflag=$?
795
796shopt -o errexit >/dev/null
797echo set=$?
798
799## STDOUT:
800shopt -u xpg_echo
801q=1
802shopt -s xpg_echo
803shopt -u xpg_echo
804p=1
805noflag=1
806set=1
807## END
808
809## N-I dash/mksh STDOUT:
810## END
811
812#### shopt -p exit code (regression)
813case $SH in dash|mksh) exit ;; esac
814
815shopt -p > /dev/null
816echo status=$?
817
818## STDOUT:
819status=0
820## END
821
822## N-I dash/mksh STDOUT:
823## END
824
825#### no-ops not shown by shopt -p
826
827shopt -p | grep xpg
828echo --
829## STDOUT:
830--
831## END
832## OK bash STDOUT:
833shopt -u xpg_echo
834--
835## END
836
837