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

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