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

758 lines, 405 significant
1# Test set flags, sh flags.
2
3## compare_shells: bash dash mksh
4## oils_failures_allowed: 1
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
327rm $TMP/no-clobber
328set -C
329echo foo > $TMP/no-clobber
330echo $?
331echo foo > $TMP/no-clobber
332echo $?
333echo foo >| $TMP/no-clobber
334echo $?
335## stdout-json: "0\n1\n0\n"
336## OK dash stdout-json: "0\n2\n0\n"
337
338#### noclobber on <>
339set -C
340echo foo >| $TMP/no-clobber
341exec 3<> $TMP/no-clobber
342read -n 1 <&3
343echo -n . >&3
344exec 3>&-
345cat $TMP/no-clobber
346## stdout-json: "f.o\n"
347## N-I dash stdout-json: ".oo\n"
348
349#### SHELLOPTS is updated when options are changed
350echo $SHELLOPTS | grep -q xtrace
351echo $?
352set -x
353echo $SHELLOPTS | grep -q xtrace
354echo $?
355set +x
356echo $SHELLOPTS | grep -q xtrace
357echo $?
358## STDOUT:
3591
3600
3611
362## END
363## N-I dash/mksh STDOUT:
3641
3651
3661
367## END
368
369#### SHELLOPTS is readonly
370SHELLOPTS=x
371echo status=$?
372## stdout: status=1
373## N-I dash/mksh stdout: status=0
374
375# Setting a readonly variable in osh is a hard failure.
376## OK osh status: 1
377## OK osh stdout-json: ""
378
379#### SHELLOPTS and BASHOPTS are set
380
381# 2024-06 - tickled by Samuel testing Gentoo
382
383# bash: bracexpand:hashall etc.
384
385echo shellopts ${SHELLOPTS:?} > /dev/null
386echo bashopts ${BASHOPTS:?} > /dev/null
387
388## STDOUT:
389## END
390
391## N-I dash status: 2
392## N-I mksh status: 1
393
394
395#### set - -
396set a b
397echo "$@"
398set - a b
399echo "$@"
400set -- a b
401echo "$@"
402set - -
403echo "$@"
404set - +
405echo "$@"
406set + -
407echo "$@"
408set -- --
409echo "$@"
410
411# note: zsh is different, and yash is totally different
412## STDOUT:
413a b
414a b
415a b
416-
417+
418+
419--
420## END
421## OK osh/yash STDOUT:
422a b
423- a b
424a b
425- -
426- +
427+ -
428--
429## END
430## BUG mksh STDOUT:
431a b
432a b
433a b
434-
435+
436-
437--
438## END
439## BUG zsh STDOUT:
440a b
441a b
442a b
443
444+
445
446--
447## END
448
449#### set -o lists options
450# NOTE: osh doesn't use the same format yet.
451set -o | grep -o noexec
452## STDOUT:
453noexec
454## END
455
456#### set without args lists variables
457__GLOBAL=g
458f() {
459 local __mylocal=L
460 local __OTHERLOCAL=L
461 __GLOBAL=mutated
462 set | grep '^__'
463}
464g() {
465 local __var_in_parent_scope=D
466 f
467}
468g
469## status: 0
470## STDOUT:
471__GLOBAL=mutated
472__OTHERLOCAL=L
473__mylocal=L
474__var_in_parent_scope=D
475## END
476## OK mksh STDOUT:
477__GLOBAL=mutated
478__var_in_parent_scope=D
479__OTHERLOCAL=L
480__mylocal=L
481## END
482## OK dash STDOUT:
483__GLOBAL='mutated'
484__OTHERLOCAL='L'
485__mylocal='L'
486__var_in_parent_scope='D'
487## END
488
489#### 'set' and 'eval' round trip
490
491# NOTE: not testing arrays and associative arrays!
492_space='[ ]'
493_whitespace=$'[\t\r\n]'
494_sq="'single quotes'"
495_backslash_dq="\\ \""
496_unicode=$'[\u03bc]'
497
498# Save the variables
499varfile=$TMP/vars-$(basename $SH).txt
500
501set | grep '^_' > "$varfile"
502
503# Unset variables
504unset _space _whitespace _sq _backslash_dq _unicode
505echo [ $_space $_whitespace $_sq $_backslash_dq $_unicode ]
506
507# Restore them
508
509. $varfile
510echo "Code saved to $varfile" 1>&2 # for debugging
511
512test "$_space" = '[ ]' && echo OK
513test "$_whitespace" = $'[\t\r\n]' && echo OK
514test "$_sq" = "'single quotes'" && echo OK
515test "$_backslash_dq" = "\\ \"" && echo OK
516test "$_unicode" = $'[\u03bc]' && echo OK
517
518## STDOUT:
519[ ]
520OK
521OK
522OK
523OK
524OK
525## END
526
527#### set without args and array variables (not in OSH)
528declare -a __array
529__array=(1 2 '3 4')
530set | grep '^__'
531## STDOUT:
532__array=([0]="1" [1]="2" [2]="3 4")
533## END
534## OK mksh STDOUT:
535__array[0]=1
536__array[1]=2
537__array[2]='3 4'
538## N-I dash stdout-json: ""
539## N-I dash status: 2
540## N-I osh stdout-json: ""
541## N-I osh status: 1
542
543#### set without args and assoc array variables (not in OSH)
544typeset -A __assoc
545__assoc['k e y']='v a l'
546__assoc[a]=b
547set | grep '^__'
548## STDOUT:
549__assoc=([a]="b" ["k e y"]="v a l" )
550## END
551## N-I mksh stdout-json: ""
552## N-I mksh status: 1
553## N-I dash stdout-json: ""
554## N-I dash status: 1
555## N-I osh stdout-json: ""
556## N-I osh status: 1
557
558#### shopt -q
559shopt -q nullglob
560echo nullglob=$?
561
562# set it
563shopt -s nullglob
564
565shopt -q nullglob
566echo nullglob=$?
567
568shopt -q nullglob failglob
569echo nullglob,failglob=$?
570
571# set it
572shopt -s failglob
573shopt -q nullglob failglob
574echo nullglob,failglob=$?
575
576## STDOUT:
577nullglob=1
578nullglob=0
579nullglob,failglob=1
580nullglob,failglob=0
581## END
582## N-I dash/mksh STDOUT:
583nullglob=127
584nullglob=127
585nullglob,failglob=127
586nullglob,failglob=127
587## END
588
589#### shopt -q invalid
590shopt -q invalidZZ
591echo invalidZZ=$?
592## STDOUT:
593invalidZZ=2
594## END
595## OK bash STDOUT:
596invalidZZ=1
597## END
598## N-I dash/mksh STDOUT:
599invalidZZ=127
600## END
601
602#### shopt -s strict:all
603n=2
604
605show-strict() {
606 shopt -p | grep 'strict_' | head -n $n
607 echo -
608}
609
610show-strict
611shopt -s strict:all
612show-strict
613shopt -u strict_arith
614show-strict
615## STDOUT:
616shopt -u strict_argv
617shopt -u strict_arith
618-
619shopt -s strict_argv
620shopt -s strict_arith
621-
622shopt -s strict_argv
623shopt -u strict_arith
624-
625## END
626## N-I dash status: 2
627## N-I dash stdout-json: ""
628## N-I bash/mksh STDOUT:
629-
630-
631-
632## END
633
634#### shopt allows for backward compatibility like bash
635
636# doesn't have to be on, but just for testing
637set -o errexit
638
639shopt -p nullglob || true # bash returns 1 here? Like -q.
640
641# This should set nullglob, and return 1, which can be ignored
642shopt -s nullglob strict_OPTION_NOT_YET_IMPLEMENTED 2>/dev/null || true
643echo status=$?
644
645shopt -p nullglob || true
646
647## STDOUT:
648shopt -u nullglob
649status=0
650shopt -s nullglob
651## END
652## N-I dash/mksh STDOUT:
653status=0
654## END
655## N-I dash/mksh status: 0
656
657#### shopt -p validates option names
658shopt -p nullglob invalid failglob
659echo status=$?
660# same thing as -p, slightly different format in bash
661shopt nullglob invalid failglob > $TMP/out.txt
662status=$?
663sed --regexp-extended 's/\s+/ /' $TMP/out.txt # make it easier to assert
664echo status=$status
665## STDOUT:
666status=2
667status=2
668## END
669## OK bash STDOUT:
670shopt -u nullglob
671shopt -u failglob
672status=1
673nullglob off
674failglob off
675status=1
676## END
677## N-I dash/mksh STDOUT:
678status=127
679status=127
680## END
681
682#### shopt -p -o validates option names
683shopt -p -o errexit invalid nounset
684echo status=$?
685## STDOUT:
686set +o errexit
687status=2
688## END
689## OK bash STDOUT:
690set +o errexit
691set +o nounset
692status=1
693## END
694## N-I dash/mksh STDOUT:
695status=127
696## END
697
698#### stubbed out bash options
699for name in foo autocd cdable_vars checkwinsize; do
700 shopt -s $name
701 echo $?
702done
703## STDOUT:
7042
7050
7060
7070
708## END
709## OK bash STDOUT:
7101
7110
7120
7130
714## END
715## OK dash/mksh STDOUT:
716127
717127
718127
719127
720## END
721
722#### shopt -s nounset works in Oil, not in bash
723case $SH in
724 *dash|*mksh)
725 echo N-I
726 exit
727 ;;
728esac
729shopt -s nounset
730echo status=$?
731
732# get rid of extra space in bash output
733set -o | grep nounset | sed 's/[ \t]\+/ /g'
734
735## STDOUT:
736status=0
737set -o nounset
738## END
739## OK bash STDOUT:
740status=1
741nounset off
742# END
743## N-I dash/mksh STDOUT:
744N-I
745## END
746
747#### no-ops not in shopt -p output
748shopt -p | grep xpg
749echo --
750## STDOUT:
751--
752## END
753## OK bash STDOUT:
754shopt -u xpg_echo
755--
756## END
757
758