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

791 lines, 386 significant
1## oils_failures_allowed: 3
2## compare_shells: dash bash-4.4 mksh zsh
3
4
5# NOTE:
6# - $! is tested in background.test.sh
7# - $- is tested in sh-options
8#
9# TODO: It would be nice to make a table, like:
10#
11# $$ $BASHPID $PPID $SHLVL $BASH_SUBSHELL
12# X
13# (Subshell, Command Sub, Pipeline, Spawn $0)
14#
15# And see whether the variable changed.
16
17#### $PWD is set
18# Just test that it has a slash for now.
19echo $PWD | grep /
20## status: 0
21
22#### $PWD is not only set, but exported
23env | grep PWD
24## status: 0
25## BUG mksh status: 1
26
27#### $PATH is set if unset at startup
28
29# WORKAROUND for Python version of bin/osh -- we can't run bin/oils_for_unix.py
30# because it a shebang #!/usr/bin/env python2
31# This test is still useful for the C++ oils-for-unix.
32
33case $SH in
34 */bin/osh)
35 echo yes
36 echo yes
37 exit
38 ;;
39esac
40
41# Get absolute path before changing PATH
42sh=$(which $SH)
43
44old_path=$PATH
45unset PATH
46
47$sh -c 'echo $PATH' > path.txt
48
49PATH=$old_path
50
51# looks like PATH=/usr/bin:/bin for mksh, but more complicated for others
52# cat path.txt
53
54# should contain /usr/bin
55if egrep -q '(^|:)/usr/bin($|:)' path.txt; then
56 echo yes
57fi
58
59# should contain /bin
60if egrep -q '(^|:)/bin($|:)' path.txt ; then
61 echo yes
62fi
63
64## STDOUT:
65yes
66yes
67## END
68
69#### $HOME is NOT set
70case $SH in *zsh) echo 'zsh sets HOME'; exit ;; esac
71
72home=$(echo $HOME)
73test "$home" = ""
74echo status=$?
75
76env | grep HOME
77echo status=$?
78
79# not in interactive shell either
80$SH -i -c 'echo $HOME' | grep /
81echo status=$?
82
83## STDOUT:
84status=0
85status=1
86status=1
87## END
88## BUG zsh STDOUT:
89zsh sets HOME
90## END
91
92#### Vars set interactively only: $HISTFILE
93case $SH in dash|mksh|zsh) exit ;; esac
94
95$SH --norc --rcfile /dev/null -c 'echo histfile=${HISTFILE:+yes}'
96$SH --norc --rcfile /dev/null -i -c 'echo histfile=${HISTFILE:+yes}'
97
98## STDOUT:
99histfile=
100histfile=yes
101## END
102
103## N-I dash/mksh/zsh STDOUT:
104## END
105
106#### Some vars are set, even without startup file, or env: PATH, PWD
107
108flags=''
109case $SH in
110 dash) exit ;;
111 bash*)
112 flags='--noprofile --norc --rcfile /devnull'
113 ;;
114 osh)
115 flags='--rcfile /devnull'
116 ;;
117esac
118
119sh_path=$(which $SH)
120
121case $sh_path in
122 */bin/osh)
123 # Hack for running with Python2
124 export PYTHONPATH="$REPO_ROOT:$REPO_ROOT/vendor"
125 sh_prefix="$(which python2) $REPO_ROOT/bin/oils_for_unix.py osh"
126 ;;
127 *)
128 sh_prefix=$sh_path
129 ;;
130esac
131
132#echo PATH=$PATH
133
134
135# mksh has typeset, not declare
136# bash exports PWD, but not PATH PS4
137
138/usr/bin/env -i PYTHONPATH=$PYTHONPATH $sh_prefix $flags -c 'typeset -p PATH PWD PS4' >&2
139echo path pwd ps4 $?
140
141/usr/bin/env -i PYTHONPATH=$PYTHONPATH $sh_prefix $flags -c 'typeset -p SHELLOPTS' >&2
142echo shellopts $?
143
144# bash doesn't set HOME, mksh and zsh do
145/usr/bin/env -i PYTHONPATH=$PYTHONPATH $sh_prefix $flags -c 'typeset -p HOME PS1' >&2
146echo home ps1 $?
147
148# IFS is set, but not exported
149/usr/bin/env -i PYTHONPATH=$PYTHONPATH $sh_prefix $flags -c 'typeset -p IFS' >&2
150echo ifs $?
151
152## STDOUT:
153path pwd ps4 0
154shellopts 0
155home ps1 1
156ifs 0
157## END
158
159## OK mksh STDOUT:
160path pwd ps4 0
161shellopts 0
162home ps1 0
163ifs 0
164## END
165
166## OK zsh STDOUT:
167path pwd ps4 0
168shellopts 1
169home ps1 0
170ifs 0
171## END
172
173## N-I dash STDOUT:
174## END
175
176#### UID EUID PPID can't be changed
177
178# bash makes these 3 read-only
179{
180 UID=xx $SH -c 'echo uid=$UID'
181
182 EUID=xx $SH -c 'echo euid=$EUID'
183
184 PPID=xx $SH -c 'echo ppid=$PPID'
185
186} > out.txt
187
188# bash shows that vars are readonly
189# zsh shows other errors
190# cat out.txt
191#echo
192
193grep '=xx' out.txt
194echo status=$?
195
196## STDOUT:
197status=1
198## END
199## BUG dash/mksh STDOUT:
200uid=xx
201euid=xx
202status=0
203## END
204
205#### HOSTNAME OSTYPE can be changed
206case $SH in zsh) exit ;; esac
207
208#$SH -c 'echo hostname=$HOSTNAME'
209
210HOSTNAME=x $SH -c 'echo hostname=$HOSTNAME'
211OSTYPE=x $SH -c 'echo ostype=$OSTYPE'
212echo
213
214#PS4=x $SH -c 'echo ps4=$PS4'
215
216# OPTIND is special
217#OPTIND=xx $SH -c 'echo optind=$OPTIND'
218
219
220## STDOUT:
221hostname=x
222ostype=x
223
224## END
225
226## BUG zsh STDOUT:
227## END
228
229
230#### $1 .. $9 are scoped, while $0 is not
231fun() {
232 case $0 in
233 *sh)
234 echo 'sh'
235 ;;
236 *sh-*) # bash-4.4 is OK
237 echo 'sh'
238 ;;
239 esac
240
241 echo $1 $2
242}
243fun a b
244
245## STDOUT:
246sh
247a b
248## END
249## BUG zsh STDOUT:
250a b
251## END
252
253#### $?
254echo $? # starts out as 0
255sh -c 'exit 33'
256echo $?
257## STDOUT:
2580
25933
260## END
261## status: 0
262
263#### $#
264set -- 1 2 3 4
265echo $#
266## stdout: 4
267## status: 0
268
269#### $$ looks like a PID
270# Just test that it has decimal digits
271echo $$ | egrep '[0-9]+'
272## status: 0
273
274#### $$ doesn't change with subshell or command sub
275# Just test that it has decimal digits
276set -o errexit
277die() {
278 echo 1>&2 "$@"; exit 1
279}
280parent=$$
281test -n "$parent" || die "empty PID in parent"
282( child=$$
283 test -n "$child" || die "empty PID in subshell"
284 test "$parent" = "$child" || die "should be equal: $parent != $child"
285 echo 'subshell OK'
286)
287echo $( child=$$
288 test -n "$child" || die "empty PID in command sub"
289 test "$parent" = "$child" || die "should be equal: $parent != $child"
290 echo 'command sub OK'
291 )
292exit 3 # make sure we got here
293## status: 3
294## STDOUT:
295subshell OK
296command sub OK
297## END
298
299#### $BASHPID DOES change with subshell and command sub
300set -o errexit
301die() {
302 echo 1>&2 "$@"; exit 1
303}
304parent=$BASHPID
305test -n "$parent" || die "empty BASHPID in parent"
306( child=$BASHPID
307 test -n "$child" || die "empty BASHPID in subshell"
308 test "$parent" != "$child" || die "should not be equal: $parent = $child"
309 echo 'subshell OK'
310)
311echo $( child=$BASHPID
312 test -n "$child" || die "empty BASHPID in command sub"
313 test "$parent" != "$child" ||
314 die "should not be equal: $parent = $child"
315 echo 'command sub OK'
316 )
317exit 3 # make sure we got here
318
319# mksh also implements BASHPID!
320
321## status: 3
322## STDOUT:
323subshell OK
324command sub OK
325## END
326## N-I dash/zsh status: 1
327## N-I dash/zsh stdout-json: ""
328
329#### Background PID $! looks like a PID
330sleep 0.01 &
331pid=$!
332wait
333echo $pid | egrep '[0-9]+' >/dev/null
334echo status=$?
335## stdout: status=0
336
337#### $PPID
338echo $PPID | egrep '[0-9]+'
339## status: 0
340
341# NOTE: There is also $BASHPID
342
343#### $PIPESTATUS
344echo hi | sh -c 'cat; exit 33' | wc -l >/dev/null
345argv.py "${PIPESTATUS[@]}"
346## status: 0
347## STDOUT:
348['0', '33', '0']
349## END
350## N-I dash stdout-json: ""
351## N-I dash status: 2
352## N-I zsh STDOUT:
353['']
354## END
355
356#### $RANDOM
357expr $0 : '.*/osh$' && exit 99 # Disabled because of spec-runner.sh issue
358echo $RANDOM | egrep '[0-9]+'
359## status: 0
360## N-I dash status: 1
361
362#### $UID and $EUID
363# These are both bash-specific.
364set -o errexit
365echo $UID | egrep -o '[0-9]+' >/dev/null
366echo $EUID | egrep -o '[0-9]+' >/dev/null
367echo status=$?
368## stdout: status=0
369## N-I dash/mksh stdout-json: ""
370## N-I dash/mksh status: 1
371
372#### $OSTYPE is non-empty
373test -n "$OSTYPE"
374echo status=$?
375## STDOUT:
376status=0
377## END
378## N-I dash/mksh STDOUT:
379status=1
380## END
381
382#### $HOSTNAME
383test "$HOSTNAME" = "$(hostname)"
384echo status=$?
385## STDOUT:
386status=0
387## END
388## N-I dash/mksh/zsh STDOUT:
389status=1
390## END
391
392#### $LINENO is the current line, not line of function call
393echo $LINENO # first line
394g() {
395 argv.py $LINENO # line 3
396}
397f() {
398 argv.py $LINENO # line 6
399 g
400 argv.py $LINENO # line 8
401}
402f
403## STDOUT:
4041
405['6']
406['3']
407['8']
408## END
409## BUG zsh STDOUT:
4101
411['1']
412['1']
413['3']
414## END
415## BUG dash STDOUT:
4161
417['2']
418['2']
419['4']
420## END
421
422#### $LINENO in "bare" redirect arg (bug regression)
423filename=$TMP/bare3
424rm -f $filename
425> $TMP/bare$LINENO
426test -f $filename && echo written
427echo $LINENO
428## STDOUT:
429written
4305
431## END
432## BUG zsh STDOUT:
433## END
434
435#### $LINENO in redirect arg (bug regression)
436filename=$TMP/lineno_regression3
437rm -f $filename
438echo x > $TMP/lineno_regression$LINENO
439test -f $filename && echo written
440echo $LINENO
441## STDOUT:
442written
4435
444## END
445
446#### $LINENO in [[
447echo one
448[[ $LINENO -eq 2 ]] && echo OK
449## STDOUT:
450one
451OK
452## END
453## N-I dash status: 127
454## N-I dash stdout: one
455## N-I mksh status: 1
456## N-I mksh stdout: one
457
458#### $LINENO in ((
459echo one
460(( x = LINENO ))
461echo $x
462## STDOUT:
463one
4642
465## END
466## N-I dash STDOUT:
467one
468
469## END
470
471#### $LINENO in for loop
472# hm bash doesn't take into account the word break. That's OK; we won't either.
473echo one
474for x in \
475 $LINENO zzz; do
476 echo $x
477done
478## STDOUT:
479one
4802
481zzz
482## END
483## OK mksh STDOUT:
484one
4851
486zzz
487## END
488
489#### $LINENO in other for loops
490set -- a b c
491for x; do
492 echo $LINENO $x
493done
494## STDOUT:
4953 a
4963 b
4973 c
498## END
499
500#### $LINENO in for (( loop
501# This is a real edge case that I'm not sure we care about. We would have to
502# change the span ID inside the loop to make it really correct.
503echo one
504for (( i = 0; i < $LINENO; i++ )); do
505 echo $i
506done
507## STDOUT:
508one
5090
5101
511## END
512## N-I dash stdout: one
513## N-I dash status: 2
514## BUG mksh stdout: one
515## BUG mksh status: 1
516
517#### $LINENO for assignment
518a1=$LINENO a2=$LINENO
519b1=$LINENO b2=$LINENO
520echo $a1 $a2
521echo $b1 $b2
522## STDOUT:
5231 1
5242 2
525## END
526
527#### $LINENO in case
528case $LINENO in
529 1) echo 'got line 1' ;;
530 *) echo line=$LINENO
531esac
532## STDOUT:
533got line 1
534## END
535## BUG mksh STDOUT:
536line=3
537## END
538
539#### $_ with simple command and evaluation
540
541name=world
542echo "hi $name"
543echo "$_"
544## STDOUT:
545hi world
546hi world
547## END
548## N-I dash/mksh STDOUT:
549hi world
550
551## END
552
553#### $_ and ${_}
554case $SH in (dash|mksh) exit ;; esac
555
556_var=value
557
558: 42
559echo $_ $_var ${_}var
560
561: 'foo'"bar"
562echo $_
563
564## STDOUT:
56542 value 42var
566foobar
567## END
568## N-I dash/mksh stdout-json: ""
569
570#### $_ with word splitting
571case $SH in (dash|mksh) exit ;; esac
572
573setopt shwordsplit # for ZSH
574
575x='with spaces'
576: $x
577echo $_
578
579## STDOUT:
580spaces
581## END
582## N-I dash/mksh stdout-json: ""
583
584#### $_ with pipeline and subshell
585case $SH in (dash|mksh) exit ;; esac
586
587shopt -s lastpipe
588
589seq 3 | echo last=$_
590
591echo pipeline=$_
592
593( echo subshell=$_ )
594echo done=$_
595
596## STDOUT:
597last=
598pipeline=last=
599subshell=pipeline=last=
600done=pipeline=last=
601## END
602
603# very weird semantics for zsh!
604## OK zsh STDOUT:
605last=3
606pipeline=last=3
607subshell=
608done=
609## END
610
611## N-I dash/mksh stdout-json: ""
612
613
614#### $_ with && and ||
615case $SH in (dash|mksh) exit ;; esac
616
617echo hi && echo last=$_
618echo and=$_
619
620echo hi || echo last=$_
621echo or=$_
622
623## STDOUT:
624hi
625last=hi
626and=last=hi
627hi
628or=hi
629## END
630
631## N-I dash/mksh stdout-json: ""
632
633#### $_ is not reset with (( and [[
634
635# bash is inconsistent because it does it for pipelines and assignments, but
636# not (( and [[
637
638case $SH in (dash|mksh) exit ;; esac
639
640echo simple
641(( a = 2 + 3 ))
642echo "(( $_"
643
644[[ a == *.py ]]
645echo "[[ $_"
646
647## STDOUT:
648simple
649(( simple
650[[ (( simple
651## END
652
653## N-I dash/mksh stdout-json: ""
654
655
656#### $_ with assignments, arrays, etc.
657case $SH in (dash|mksh) exit ;; esac
658
659: foo
660echo "colon [$_]"
661
662s=bar
663echo "bare assign [$_]"
664
665# zsh uses declare; bash uses s=bar
666declare s=bar
667echo "declare [$_]"
668
669# zsh remains s:declare, bash resets it
670a=(1 2)
671echo "array [$_]"
672
673# zsh sets it to declare, bash uses the LHS a
674declare a=(1 2)
675echo "declare array [$_]"
676
677declare -g d=(1 2)
678echo "declare flag [$_]"
679
680## STDOUT:
681colon [foo]
682bare assign []
683declare [s=bar]
684array []
685declare array [a]
686declare flag [d]
687## END
688
689## OK zsh STDOUT:
690colon [foo]
691bare assign []
692declare [declare]
693array [declare [declare]]
694declare array [declare]
695declare flag [-g]
696## END
697
698## OK osh STDOUT:
699colon [foo]
700bare assign [colon [foo]]
701declare [bare assign [colon [foo]]]
702array [declare [bare assign [colon [foo]]]]
703declare array [array [declare [bare assign [colon [foo]]]]]
704declare flag [declare array [array [declare [bare assign [colon [foo]]]]]]
705## END
706
707## N-I dash/mksh stdout-json: ""
708
709#### $_ with loop
710
711case $SH in (dash|mksh) exit ;; esac
712
713# zsh resets it when in a loop
714
715echo init
716echo begin=$_
717for x in 1 2 3; do
718 echo prev=$_
719done
720
721## STDOUT:
722init
723begin=init
724prev=begin=init
725prev=prev=begin=init
726prev=prev=prev=begin=init
727## END
728
729## OK zsh STDOUT:
730init
731begin=init
732prev=
733prev=prev=
734prev=prev=prev=
735## END
736## N-I dash/mksh stdout-json: ""
737
738
739#### $_ is not undefined on first use
740set -e
741
742x=$($SH -u -c 'echo prev=$_')
743echo status=$?
744
745# bash and mksh set $_ to $0 at first; zsh is empty
746#echo "$x"
747
748## STDOUT:
749status=0
750## END
751
752## N-I dash status: 2
753## N-I dash stdout-json: ""
754
755#### BASH_VERSION / OILS_VERSION
756case $SH in
757 bash*)
758 # BASH_VERSION=zz
759
760 echo $BASH_VERSION | egrep -o '4\.4\.0' > /dev/null
761 echo matched=$?
762 ;;
763 *osh)
764 # note: version string is mutable like in bash. I guess that's useful for
765 # testing? We might want a strict mode to eliminate that?
766
767 echo $OILS_VERSION | egrep -o '[0-9]+\.[0-9]+\.' > /dev/null
768 echo matched=$?
769 ;;
770 *)
771 echo 'no version'
772 ;;
773esac
774## STDOUT:
775matched=0
776## END
777## N-I dash/mksh/zsh STDOUT:
778no version
779## END
780
781#### $SECONDS
782
783# most likely 0 seconds, but in CI I've seen 1 second
784echo $SECONDS | awk '/[0-9]+/ { print "ok" }'
785
786## status: 0
787## STDOUT:
788ok
789## END
790## N-I dash STDOUT:
791## END