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

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