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

782 lines, 379 significant
1## oils_failures_allowed: 4
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 PS4' >&2
146echo home ps1 $?
147
148# bash doesn't set PS1, mksh and zsh do
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 echo $0 | grep -o 'sh'
233 echo $1 $2
234}
235fun a b
236
237## STDOUT:
238sh
239a b
240## END
241## BUG zsh STDOUT:
242a b
243## END
244
245#### $?
246echo $? # starts out as 0
247sh -c 'exit 33'
248echo $?
249## STDOUT:
2500
25133
252## END
253## status: 0
254
255#### $#
256set -- 1 2 3 4
257echo $#
258## stdout: 4
259## status: 0
260
261#### $$ looks like a PID
262# Just test that it has decimal digits
263echo $$ | egrep '[0-9]+'
264## status: 0
265
266#### $$ doesn't change with subshell or command sub
267# Just test that it has decimal digits
268set -o errexit
269die() {
270 echo 1>&2 "$@"; exit 1
271}
272parent=$$
273test -n "$parent" || die "empty PID in parent"
274( child=$$
275 test -n "$child" || die "empty PID in subshell"
276 test "$parent" = "$child" || die "should be equal: $parent != $child"
277 echo 'subshell OK'
278)
279echo $( child=$$
280 test -n "$child" || die "empty PID in command sub"
281 test "$parent" = "$child" || die "should be equal: $parent != $child"
282 echo 'command sub OK'
283 )
284exit 3 # make sure we got here
285## status: 3
286## STDOUT:
287subshell OK
288command sub OK
289## END
290
291#### $BASHPID DOES change with subshell and command sub
292set -o errexit
293die() {
294 echo 1>&2 "$@"; exit 1
295}
296parent=$BASHPID
297test -n "$parent" || die "empty BASHPID in parent"
298( child=$BASHPID
299 test -n "$child" || die "empty BASHPID in subshell"
300 test "$parent" != "$child" || die "should not be equal: $parent = $child"
301 echo 'subshell OK'
302)
303echo $( child=$BASHPID
304 test -n "$child" || die "empty BASHPID in command sub"
305 test "$parent" != "$child" ||
306 die "should not be equal: $parent = $child"
307 echo 'command sub OK'
308 )
309exit 3 # make sure we got here
310
311# mksh also implements BASHPID!
312
313## status: 3
314## STDOUT:
315subshell OK
316command sub OK
317## END
318## N-I dash/zsh status: 1
319## N-I dash/zsh stdout-json: ""
320
321#### Background PID $! looks like a PID
322sleep 0.01 &
323pid=$!
324wait
325echo $pid | egrep '[0-9]+' >/dev/null
326echo status=$?
327## stdout: status=0
328
329#### $PPID
330echo $PPID | egrep '[0-9]+'
331## status: 0
332
333# NOTE: There is also $BASHPID
334
335#### $PIPESTATUS
336echo hi | sh -c 'cat; exit 33' | wc -l >/dev/null
337argv.py "${PIPESTATUS[@]}"
338## status: 0
339## STDOUT:
340['0', '33', '0']
341## END
342## N-I dash stdout-json: ""
343## N-I dash status: 2
344## N-I zsh STDOUT:
345['']
346## END
347
348#### $RANDOM
349expr $0 : '.*/osh$' && exit 99 # Disabled because of spec-runner.sh issue
350echo $RANDOM | egrep '[0-9]+'
351## status: 0
352## N-I dash status: 1
353
354#### $UID and $EUID
355# These are both bash-specific.
356set -o errexit
357echo $UID | egrep -o '[0-9]+' >/dev/null
358echo $EUID | egrep -o '[0-9]+' >/dev/null
359echo status=$?
360## stdout: status=0
361## N-I dash/mksh stdout-json: ""
362## N-I dash/mksh status: 1
363
364#### $OSTYPE is non-empty
365test -n "$OSTYPE"
366echo status=$?
367## STDOUT:
368status=0
369## END
370## N-I dash/mksh STDOUT:
371status=1
372## END
373
374#### $HOSTNAME
375test "$HOSTNAME" = "$(hostname)"
376echo status=$?
377## STDOUT:
378status=0
379## END
380## N-I dash/mksh/zsh STDOUT:
381status=1
382## END
383
384#### $LINENO is the current line, not line of function call
385echo $LINENO # first line
386g() {
387 argv.py $LINENO # line 3
388}
389f() {
390 argv.py $LINENO # line 6
391 g
392 argv.py $LINENO # line 8
393}
394f
395## STDOUT:
3961
397['6']
398['3']
399['8']
400## END
401## BUG zsh STDOUT:
4021
403['1']
404['1']
405['3']
406## END
407## BUG dash STDOUT:
4081
409['2']
410['2']
411['4']
412## END
413
414#### $LINENO in "bare" redirect arg (bug regression)
415filename=$TMP/bare3
416rm -f $filename
417> $TMP/bare$LINENO
418test -f $filename && echo written
419echo $LINENO
420## STDOUT:
421written
4225
423## END
424## BUG zsh STDOUT:
425## END
426
427#### $LINENO in redirect arg (bug regression)
428filename=$TMP/lineno_regression3
429rm -f $filename
430echo x > $TMP/lineno_regression$LINENO
431test -f $filename && echo written
432echo $LINENO
433## STDOUT:
434written
4355
436## END
437
438#### $LINENO in [[
439echo one
440[[ $LINENO -eq 2 ]] && echo OK
441## STDOUT:
442one
443OK
444## END
445## N-I dash status: 127
446## N-I dash stdout: one
447## N-I mksh status: 1
448## N-I mksh stdout: one
449
450#### $LINENO in ((
451echo one
452(( x = LINENO ))
453echo $x
454## STDOUT:
455one
4562
457## END
458## N-I dash stdout-json: "one\n\n"
459
460#### $LINENO in for loop
461# hm bash doesn't take into account the word break. That's OK; we won't either.
462echo one
463for x in \
464 $LINENO zzz; do
465 echo $x
466done
467## STDOUT:
468one
4692
470zzz
471## END
472## OK mksh STDOUT:
473one
4741
475zzz
476## END
477
478#### $LINENO in other for loops
479set -- a b c
480for x; do
481 echo $LINENO $x
482done
483## STDOUT:
4843 a
4853 b
4863 c
487## END
488
489#### $LINENO in for (( loop
490# This is a real edge case that I'm not sure we care about. We would have to
491# change the span ID inside the loop to make it really correct.
492echo one
493for (( i = 0; i < $LINENO; i++ )); do
494 echo $i
495done
496## STDOUT:
497one
4980
4991
500## END
501## N-I dash stdout: one
502## N-I dash status: 2
503## BUG mksh stdout: one
504## BUG mksh status: 1
505
506#### $LINENO for assignment
507a1=$LINENO a2=$LINENO
508b1=$LINENO b2=$LINENO
509echo $a1 $a2
510echo $b1 $b2
511## STDOUT:
5121 1
5132 2
514## END
515
516#### $LINENO in case
517case $LINENO in
518 1) echo 'got line 1' ;;
519 *) echo line=$LINENO
520esac
521## STDOUT:
522got line 1
523## END
524## BUG mksh STDOUT:
525line=3
526## END
527
528#### $_ with simple command and evaluation
529
530name=world
531echo "hi $name"
532echo "$_"
533## STDOUT:
534hi world
535hi world
536## END
537## N-I dash/mksh STDOUT:
538hi world
539
540## END
541
542#### $_ and ${_}
543case $SH in (dash|mksh) exit ;; esac
544
545_var=value
546
547: 42
548echo $_ $_var ${_}var
549
550: 'foo'"bar"
551echo $_
552
553## STDOUT:
55442 value 42var
555foobar
556## END
557## N-I dash/mksh stdout-json: ""
558
559#### $_ with word splitting
560case $SH in (dash|mksh) exit ;; esac
561
562setopt shwordsplit # for ZSH
563
564x='with spaces'
565: $x
566echo $_
567
568## STDOUT:
569spaces
570## END
571## N-I dash/mksh stdout-json: ""
572
573#### $_ with pipeline and subshell
574case $SH in (dash|mksh) exit ;; esac
575
576shopt -s lastpipe
577
578seq 3 | echo last=$_
579
580echo pipeline=$_
581
582( echo subshell=$_ )
583echo done=$_
584
585## STDOUT:
586last=
587pipeline=last=
588subshell=pipeline=last=
589done=pipeline=last=
590## END
591
592# very weird semantics for zsh!
593## OK zsh STDOUT:
594last=3
595pipeline=last=3
596subshell=
597done=
598## END
599
600## N-I dash/mksh stdout-json: ""
601
602
603#### $_ with && and ||
604case $SH in (dash|mksh) exit ;; esac
605
606echo hi && echo last=$_
607echo and=$_
608
609echo hi || echo last=$_
610echo or=$_
611
612## STDOUT:
613hi
614last=hi
615and=last=hi
616hi
617or=hi
618## END
619
620## N-I dash/mksh stdout-json: ""
621
622#### $_ is not reset with (( and [[
623
624# bash is inconsistent because it does it for pipelines and assignments, but
625# not (( and [[
626
627case $SH in (dash|mksh) exit ;; esac
628
629echo simple
630(( a = 2 + 3 ))
631echo "(( $_"
632
633[[ a == *.py ]]
634echo "[[ $_"
635
636## STDOUT:
637simple
638(( simple
639[[ (( simple
640## END
641
642## N-I dash/mksh stdout-json: ""
643
644
645#### $_ with assignments, arrays, etc.
646case $SH in (dash|mksh) exit ;; esac
647
648: foo
649echo "colon [$_]"
650
651s=bar
652echo "bare assign [$_]"
653
654# zsh uses declare; bash uses s=bar
655declare s=bar
656echo "declare [$_]"
657
658# zsh remains s:declare, bash resets it
659a=(1 2)
660echo "array [$_]"
661
662# zsh sets it to declare, bash uses the LHS a
663declare a=(1 2)
664echo "declare array [$_]"
665
666declare -g d=(1 2)
667echo "declare flag [$_]"
668
669## STDOUT:
670colon [foo]
671bare assign []
672declare [s=bar]
673array []
674declare array [a]
675declare flag [d]
676## END
677
678## OK zsh STDOUT:
679colon [foo]
680bare assign []
681declare [declare]
682array [declare [declare]]
683declare array [declare]
684declare flag [-g]
685## END
686
687## OK osh STDOUT:
688colon [foo]
689bare assign [colon [foo]]
690declare [bare assign [colon [foo]]]
691array [declare [bare assign [colon [foo]]]]
692declare array [array [declare [bare assign [colon [foo]]]]]
693declare flag [declare array [array [declare [bare assign [colon [foo]]]]]]
694## END
695
696## N-I dash/mksh stdout-json: ""
697
698#### $_ with loop
699
700case $SH in (dash|mksh) exit ;; esac
701
702# zsh resets it when in a loop
703
704echo init
705echo begin=$_
706for x in 1 2 3; do
707 echo prev=$_
708done
709
710## STDOUT:
711init
712begin=init
713prev=begin=init
714prev=prev=begin=init
715prev=prev=prev=begin=init
716## END
717
718## OK zsh STDOUT:
719init
720begin=init
721prev=
722prev=prev=
723prev=prev=prev=
724## END
725## N-I dash/mksh stdout-json: ""
726
727
728#### $_ is not undefined on first use
729set -e
730
731x=$($SH -u -c 'echo prev=$_')
732echo status=$?
733
734# bash and mksh set $_ to $0 at first; zsh is empty
735#echo "$x"
736
737## STDOUT:
738status=0
739## END
740
741## N-I dash status: 2
742## N-I dash stdout-json: ""
743
744#### BASH_VERSION / OILS_VERSION
745case $SH in
746 bash*)
747 # BASH_VERSION=zz
748
749 echo $BASH_VERSION | egrep -o '4\.4\.0' > /dev/null
750 echo matched=$?
751 ;;
752 *osh)
753 # note: version string is mutable like in bash. I guess that's useful for
754 # testing? We might want a strict mode to eliminate that?
755
756 echo $OILS_VERSION | egrep -o '[0-9]+\.[0-9]+\.' > /dev/null
757 echo matched=$?
758 ;;
759 *)
760 echo 'no version'
761 ;;
762esac
763## STDOUT:
764matched=0
765## END
766## N-I dash/mksh/zsh STDOUT:
767no version
768## END
769
770#### $SECONDS
771
772# should be zero seconds
773echo seconds=$SECONDS
774
775## status: 0
776## STDOUT:
777seconds=0
778## END
779## N-I dash STDOUT:
780seconds=
781## END
782