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

790 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-json: "one\n\n"
467
468#### $LINENO in for loop
469# hm bash doesn't take into account the word break. That's OK; we won't either.
470echo one
471for x in \
472 $LINENO zzz; do
473 echo $x
474done
475## STDOUT:
476one
4772
478zzz
479## END
480## OK mksh STDOUT:
481one
4821
483zzz
484## END
485
486#### $LINENO in other for loops
487set -- a b c
488for x; do
489 echo $LINENO $x
490done
491## STDOUT:
4923 a
4933 b
4943 c
495## END
496
497#### $LINENO in for (( loop
498# This is a real edge case that I'm not sure we care about. We would have to
499# change the span ID inside the loop to make it really correct.
500echo one
501for (( i = 0; i < $LINENO; i++ )); do
502 echo $i
503done
504## STDOUT:
505one
5060
5071
508## END
509## N-I dash stdout: one
510## N-I dash status: 2
511## BUG mksh stdout: one
512## BUG mksh status: 1
513
514#### $LINENO for assignment
515a1=$LINENO a2=$LINENO
516b1=$LINENO b2=$LINENO
517echo $a1 $a2
518echo $b1 $b2
519## STDOUT:
5201 1
5212 2
522## END
523
524#### $LINENO in case
525case $LINENO in
526 1) echo 'got line 1' ;;
527 *) echo line=$LINENO
528esac
529## STDOUT:
530got line 1
531## END
532## BUG mksh STDOUT:
533line=3
534## END
535
536#### $_ with simple command and evaluation
537
538name=world
539echo "hi $name"
540echo "$_"
541## STDOUT:
542hi world
543hi world
544## END
545## N-I dash/mksh STDOUT:
546hi world
547
548## END
549
550#### $_ and ${_}
551case $SH in (dash|mksh) exit ;; esac
552
553_var=value
554
555: 42
556echo $_ $_var ${_}var
557
558: 'foo'"bar"
559echo $_
560
561## STDOUT:
56242 value 42var
563foobar
564## END
565## N-I dash/mksh stdout-json: ""
566
567#### $_ with word splitting
568case $SH in (dash|mksh) exit ;; esac
569
570setopt shwordsplit # for ZSH
571
572x='with spaces'
573: $x
574echo $_
575
576## STDOUT:
577spaces
578## END
579## N-I dash/mksh stdout-json: ""
580
581#### $_ with pipeline and subshell
582case $SH in (dash|mksh) exit ;; esac
583
584shopt -s lastpipe
585
586seq 3 | echo last=$_
587
588echo pipeline=$_
589
590( echo subshell=$_ )
591echo done=$_
592
593## STDOUT:
594last=
595pipeline=last=
596subshell=pipeline=last=
597done=pipeline=last=
598## END
599
600# very weird semantics for zsh!
601## OK zsh STDOUT:
602last=3
603pipeline=last=3
604subshell=
605done=
606## END
607
608## N-I dash/mksh stdout-json: ""
609
610
611#### $_ with && and ||
612case $SH in (dash|mksh) exit ;; esac
613
614echo hi && echo last=$_
615echo and=$_
616
617echo hi || echo last=$_
618echo or=$_
619
620## STDOUT:
621hi
622last=hi
623and=last=hi
624hi
625or=hi
626## END
627
628## N-I dash/mksh stdout-json: ""
629
630#### $_ is not reset with (( and [[
631
632# bash is inconsistent because it does it for pipelines and assignments, but
633# not (( and [[
634
635case $SH in (dash|mksh) exit ;; esac
636
637echo simple
638(( a = 2 + 3 ))
639echo "(( $_"
640
641[[ a == *.py ]]
642echo "[[ $_"
643
644## STDOUT:
645simple
646(( simple
647[[ (( simple
648## END
649
650## N-I dash/mksh stdout-json: ""
651
652
653#### $_ with assignments, arrays, etc.
654case $SH in (dash|mksh) exit ;; esac
655
656: foo
657echo "colon [$_]"
658
659s=bar
660echo "bare assign [$_]"
661
662# zsh uses declare; bash uses s=bar
663declare s=bar
664echo "declare [$_]"
665
666# zsh remains s:declare, bash resets it
667a=(1 2)
668echo "array [$_]"
669
670# zsh sets it to declare, bash uses the LHS a
671declare a=(1 2)
672echo "declare array [$_]"
673
674declare -g d=(1 2)
675echo "declare flag [$_]"
676
677## STDOUT:
678colon [foo]
679bare assign []
680declare [s=bar]
681array []
682declare array [a]
683declare flag [d]
684## END
685
686## OK zsh STDOUT:
687colon [foo]
688bare assign []
689declare [declare]
690array [declare [declare]]
691declare array [declare]
692declare flag [-g]
693## END
694
695## OK osh STDOUT:
696colon [foo]
697bare assign [colon [foo]]
698declare [bare assign [colon [foo]]]
699array [declare [bare assign [colon [foo]]]]
700declare array [array [declare [bare assign [colon [foo]]]]]
701declare flag [declare array [array [declare [bare assign [colon [foo]]]]]]
702## END
703
704## N-I dash/mksh stdout-json: ""
705
706#### $_ with loop
707
708case $SH in (dash|mksh) exit ;; esac
709
710# zsh resets it when in a loop
711
712echo init
713echo begin=$_
714for x in 1 2 3; do
715 echo prev=$_
716done
717
718## STDOUT:
719init
720begin=init
721prev=begin=init
722prev=prev=begin=init
723prev=prev=prev=begin=init
724## END
725
726## OK zsh STDOUT:
727init
728begin=init
729prev=
730prev=prev=
731prev=prev=prev=
732## END
733## N-I dash/mksh stdout-json: ""
734
735
736#### $_ is not undefined on first use
737set -e
738
739x=$($SH -u -c 'echo prev=$_')
740echo status=$?
741
742# bash and mksh set $_ to $0 at first; zsh is empty
743#echo "$x"
744
745## STDOUT:
746status=0
747## END
748
749## N-I dash status: 2
750## N-I dash stdout-json: ""
751
752#### BASH_VERSION / OILS_VERSION
753case $SH in
754 bash*)
755 # BASH_VERSION=zz
756
757 echo $BASH_VERSION | egrep -o '4\.4\.0' > /dev/null
758 echo matched=$?
759 ;;
760 *osh)
761 # note: version string is mutable like in bash. I guess that's useful for
762 # testing? We might want a strict mode to eliminate that?
763
764 echo $OILS_VERSION | egrep -o '[0-9]+\.[0-9]+\.' > /dev/null
765 echo matched=$?
766 ;;
767 *)
768 echo 'no version'
769 ;;
770esac
771## STDOUT:
772matched=0
773## END
774## N-I dash/mksh/zsh STDOUT:
775no version
776## END
777
778#### $SECONDS
779
780# should be zero seconds
781echo seconds=$SECONDS
782
783## status: 0
784## STDOUT:
785seconds=0
786## END
787## N-I dash STDOUT:
788seconds=
789## END
790