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

721 lines, 382 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
248shopt -po nounset
249set -o nounset
250shopt -po nounset
251## STDOUT:
252set +o nounset
253set -o nounset
254## END
255## N-I dash/mksh stdout-json: ""
256## N-I dash/mksh status: 127
257
258#### shopt -p prints 'shopt' options
259shopt -p nullglob
260shopt -s nullglob
261shopt -p nullglob
262## STDOUT:
263shopt -u nullglob
264shopt -s nullglob
265## END
266## N-I dash/mksh stdout-json: ""
267## N-I dash/mksh status: 127
268
269#### shopt with no flags prints options
270cd $TMP
271
272# print specific options. OSH does it in a different format.
273shopt nullglob failglob > one.txt
274wc -l one.txt
275grep -o nullglob one.txt
276grep -o failglob one.txt
277
278# print all options
279shopt | grep nullglob | wc -l
280## STDOUT:
2812 one.txt
282nullglob
283failglob
2841
285## END
286## N-I dash/mksh STDOUT:
2870 one.txt
2880
289## END
290
291#### noclobber off
292set -o errexit
293echo foo > $TMP/can-clobber
294set +C
295echo foo > $TMP/can-clobber
296set +o noclobber
297echo foo > $TMP/can-clobber
298cat $TMP/can-clobber
299## stdout: foo
300
301#### noclobber on
302# Not implemented yet.
303rm $TMP/no-clobber
304set -C
305echo foo > $TMP/no-clobber
306echo $?
307echo foo > $TMP/no-clobber
308echo $?
309## stdout-json: "0\n1\n"
310## OK dash stdout-json: "0\n2\n"
311
312#### SHELLOPTS is updated when options are changed
313echo $SHELLOPTS | grep -q xtrace
314echo $?
315set -x
316echo $SHELLOPTS | grep -q xtrace
317echo $?
318set +x
319echo $SHELLOPTS | grep -q xtrace
320echo $?
321## STDOUT:
3221
3230
3241
325## END
326## N-I dash/mksh STDOUT:
3271
3281
3291
330## END
331
332#### SHELLOPTS is readonly
333SHELLOPTS=x
334echo status=$?
335## stdout: status=1
336## N-I dash/mksh stdout: status=0
337
338# Setting a readonly variable in osh is a hard failure.
339## OK osh status: 1
340## OK osh stdout-json: ""
341
342#### SHELLOPTS and BASHOPTS are set
343
344# 2024-06 - tickled by Samuel testing Gentoo
345
346# bash: bracexpand:hashall etc.
347
348echo shellopts ${SHELLOPTS:?} > /dev/null
349echo bashopts ${BASHOPTS:?} > /dev/null
350
351## STDOUT:
352## END
353
354## N-I dash status: 2
355## N-I mksh status: 1
356
357
358#### set - -
359set a b
360echo "$@"
361set - a b
362echo "$@"
363set -- a b
364echo "$@"
365set - -
366echo "$@"
367set - +
368echo "$@"
369set + -
370echo "$@"
371set -- --
372echo "$@"
373
374# note: zsh is different, and yash is totally different
375## STDOUT:
376a b
377a b
378a b
379-
380+
381+
382--
383## END
384## OK osh/yash STDOUT:
385a b
386- a b
387a b
388- -
389- +
390+ -
391--
392## END
393## BUG mksh STDOUT:
394a b
395a b
396a b
397-
398+
399-
400--
401## END
402## BUG zsh STDOUT:
403a b
404a b
405a b
406
407+
408
409--
410## END
411
412#### set -o lists options
413# NOTE: osh doesn't use the same format yet.
414set -o | grep -o noexec
415## STDOUT:
416noexec
417## END
418
419#### set without args lists variables
420__GLOBAL=g
421f() {
422 local __mylocal=L
423 local __OTHERLOCAL=L
424 __GLOBAL=mutated
425 set | grep '^__'
426}
427g() {
428 local __var_in_parent_scope=D
429 f
430}
431g
432## status: 0
433## STDOUT:
434__GLOBAL=mutated
435__OTHERLOCAL=L
436__mylocal=L
437__var_in_parent_scope=D
438## END
439## OK mksh STDOUT:
440__GLOBAL=mutated
441__var_in_parent_scope=D
442__OTHERLOCAL=L
443__mylocal=L
444## END
445## OK dash STDOUT:
446__GLOBAL='mutated'
447__OTHERLOCAL='L'
448__mylocal='L'
449__var_in_parent_scope='D'
450## END
451
452#### 'set' and 'eval' round trip
453
454# NOTE: not testing arrays and associative arrays!
455_space='[ ]'
456_whitespace=$'[\t\r\n]'
457_sq="'single quotes'"
458_backslash_dq="\\ \""
459_unicode=$'[\u03bc]'
460
461# Save the variables
462varfile=$TMP/vars-$(basename $SH).txt
463
464set | grep '^_' > "$varfile"
465
466# Unset variables
467unset _space _whitespace _sq _backslash_dq _unicode
468echo [ $_space $_whitespace $_sq $_backslash_dq $_unicode ]
469
470# Restore them
471
472. $varfile
473echo "Code saved to $varfile" 1>&2 # for debugging
474
475test "$_space" = '[ ]' && echo OK
476test "$_whitespace" = $'[\t\r\n]' && echo OK
477test "$_sq" = "'single quotes'" && echo OK
478test "$_backslash_dq" = "\\ \"" && echo OK
479test "$_unicode" = $'[\u03bc]' && echo OK
480
481## STDOUT:
482[ ]
483OK
484OK
485OK
486OK
487OK
488## END
489
490#### set without args and array variables (not in OSH)
491declare -a __array
492__array=(1 2 '3 4')
493set | grep '^__'
494## STDOUT:
495__array=([0]="1" [1]="2" [2]="3 4")
496## END
497## OK mksh STDOUT:
498__array[0]=1
499__array[1]=2
500__array[2]='3 4'
501## N-I dash stdout-json: ""
502## N-I dash status: 2
503## N-I osh stdout-json: ""
504## N-I osh status: 1
505
506#### set without args and assoc array variables (not in OSH)
507typeset -A __assoc
508__assoc['k e y']='v a l'
509__assoc[a]=b
510set | grep '^__'
511## STDOUT:
512__assoc=([a]="b" ["k e y"]="v a l" )
513## END
514## N-I mksh stdout-json: ""
515## N-I mksh status: 1
516## N-I dash stdout-json: ""
517## N-I dash status: 1
518## N-I osh stdout-json: ""
519## N-I osh status: 1
520
521#### shopt -q
522shopt -q nullglob
523echo nullglob=$?
524
525# set it
526shopt -s nullglob
527
528shopt -q nullglob
529echo nullglob=$?
530
531shopt -q nullglob failglob
532echo nullglob,failglob=$?
533
534# set it
535shopt -s failglob
536shopt -q nullglob failglob
537echo nullglob,failglob=$?
538
539## STDOUT:
540nullglob=1
541nullglob=0
542nullglob,failglob=1
543nullglob,failglob=0
544## END
545## N-I dash/mksh STDOUT:
546nullglob=127
547nullglob=127
548nullglob,failglob=127
549nullglob,failglob=127
550## END
551
552#### shopt -q invalid
553shopt -q invalidZZ
554echo invalidZZ=$?
555## STDOUT:
556invalidZZ=2
557## END
558## OK bash STDOUT:
559invalidZZ=1
560## END
561## N-I dash/mksh STDOUT:
562invalidZZ=127
563## END
564
565#### shopt -s strict:all
566n=2
567
568show-strict() {
569 shopt -p | grep 'strict_' | head -n $n
570 echo -
571}
572
573show-strict
574shopt -s strict:all
575show-strict
576shopt -u strict_arith
577show-strict
578## STDOUT:
579shopt -u strict_argv
580shopt -u strict_arith
581-
582shopt -s strict_argv
583shopt -s strict_arith
584-
585shopt -s strict_argv
586shopt -u strict_arith
587-
588## END
589## N-I dash status: 2
590## N-I dash stdout-json: ""
591## N-I bash/mksh STDOUT:
592-
593-
594-
595## END
596
597#### shopt allows for backward compatibility like bash
598
599# doesn't have to be on, but just for testing
600set -o errexit
601
602shopt -p nullglob || true # bash returns 1 here? Like -q.
603
604# This should set nullglob, and return 1, which can be ignored
605shopt -s nullglob strict_OPTION_NOT_YET_IMPLEMENTED 2>/dev/null || true
606echo status=$?
607
608shopt -p nullglob || true
609
610## STDOUT:
611shopt -u nullglob
612status=0
613shopt -s nullglob
614## END
615## N-I dash/mksh STDOUT:
616status=0
617## END
618## N-I dash/mksh status: 0
619
620#### shopt -p validates option names
621shopt -p nullglob invalid failglob
622echo status=$?
623# same thing as -p, slightly different format in bash
624shopt nullglob invalid failglob > $TMP/out.txt
625status=$?
626sed --regexp-extended 's/\s+/ /' $TMP/out.txt # make it easier to assert
627echo status=$status
628## STDOUT:
629status=2
630status=2
631## END
632## OK bash STDOUT:
633shopt -u nullglob
634shopt -u failglob
635status=1
636nullglob off
637failglob off
638status=1
639## END
640## N-I dash/mksh STDOUT:
641status=127
642status=127
643## END
644
645#### shopt -p -o validates option names
646shopt -p -o errexit invalid nounset
647echo status=$?
648## STDOUT:
649set +o errexit
650status=2
651## END
652## OK bash STDOUT:
653set +o errexit
654set +o nounset
655status=1
656## END
657## N-I dash/mksh STDOUT:
658status=127
659## END
660
661#### stubbed out bash options
662for name in foo autocd cdable_vars checkwinsize; do
663 shopt -s $name
664 echo $?
665done
666## STDOUT:
6672
6680
6690
6700
671## END
672## OK bash STDOUT:
6731
6740
6750
6760
677## END
678## OK dash/mksh STDOUT:
679127
680127
681127
682127
683## END
684
685#### shopt -s nounset works in Oil, not in bash
686case $SH in
687 *dash|*mksh)
688 echo N-I
689 exit
690 ;;
691esac
692shopt -s nounset
693echo status=$?
694
695# get rid of extra space in bash output
696set -o | grep nounset | sed 's/[ \t]\+/ /g'
697
698## STDOUT:
699status=0
700set -o nounset
701## END
702## OK bash STDOUT:
703status=1
704nounset off
705# END
706## N-I dash/mksh STDOUT:
707N-I
708## END
709
710#### no-ops not in shopt -p output
711shopt -p | grep xpg
712echo --
713## STDOUT:
714--
715## END
716## OK bash STDOUT:
717shopt -u xpg_echo
718--
719## END
720
721