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

645 lines, 317 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 /
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
70#### $HOME is NOT set
71case $SH in *zsh) echo 'zsh sets HOME'; exit ;; esac
72
73home=$(echo $HOME)
74test "$home" = ""
75echo status=$?
76
77env | grep HOME
78echo status=$?
79
80# not in interactive shell either
81$SH -i -c 'echo $HOME' | grep /
82echo status=$?
83
84## STDOUT:
85status=0
86status=1
87status=1
88## END
89## BUG zsh STDOUT:
90zsh sets HOME
91## END
92
93
94#### $1 .. $9 are scoped, while $0 is not
95fun() {
96 echo $0 | grep -o 'sh'
97 echo $1 $2
98}
99fun a b
100
101## STDOUT:
102sh
103a b
104## END
105## BUG zsh STDOUT:
106a b
107## END
108
109#### $?
110echo $? # starts out as 0
111sh -c 'exit 33'
112echo $?
113## STDOUT:
1140
11533
116## END
117## status: 0
118
119#### $#
120set -- 1 2 3 4
121echo $#
122## stdout: 4
123## status: 0
124
125#### $$ looks like a PID
126# Just test that it has decimal digits
127echo $$ | egrep '[0-9]+'
128## status: 0
129
130#### $$ doesn't change with subshell or command sub
131# Just test that it has decimal digits
132set -o errexit
133die() {
134 echo 1>&2 "$@"; exit 1
135}
136parent=$$
137test -n "$parent" || die "empty PID in parent"
138( child=$$
139 test -n "$child" || die "empty PID in subshell"
140 test "$parent" = "$child" || die "should be equal: $parent != $child"
141 echo 'subshell OK'
142)
143echo $( child=$$
144 test -n "$child" || die "empty PID in command sub"
145 test "$parent" = "$child" || die "should be equal: $parent != $child"
146 echo 'command sub OK'
147 )
148exit 3 # make sure we got here
149## status: 3
150## STDOUT:
151subshell OK
152command sub OK
153## END
154
155#### $BASHPID DOES change with subshell and command sub
156set -o errexit
157die() {
158 echo 1>&2 "$@"; exit 1
159}
160parent=$BASHPID
161test -n "$parent" || die "empty BASHPID in parent"
162( child=$BASHPID
163 test -n "$child" || die "empty BASHPID in subshell"
164 test "$parent" != "$child" || die "should not be equal: $parent = $child"
165 echo 'subshell OK'
166)
167echo $( child=$BASHPID
168 test -n "$child" || die "empty BASHPID in command sub"
169 test "$parent" != "$child" ||
170 die "should not be equal: $parent = $child"
171 echo 'command sub OK'
172 )
173exit 3 # make sure we got here
174
175# mksh also implements BASHPID!
176
177## status: 3
178## STDOUT:
179subshell OK
180command sub OK
181## END
182## N-I dash/zsh status: 1
183## N-I dash/zsh stdout-json: ""
184
185#### Background PID $! looks like a PID
186sleep 0.01 &
187pid=$!
188wait
189echo $pid | egrep '[0-9]+' >/dev/null
190echo status=$?
191## stdout: status=0
192
193#### $PPID
194echo $PPID | egrep '[0-9]+'
195## status: 0
196
197# NOTE: There is also $BASHPID
198
199#### $PIPESTATUS
200echo hi | sh -c 'cat; exit 33' | wc -l >/dev/null
201argv.py "${PIPESTATUS[@]}"
202## status: 0
203## STDOUT:
204['0', '33', '0']
205## END
206## N-I dash stdout-json: ""
207## N-I dash status: 2
208## N-I zsh STDOUT:
209['']
210## END
211
212#### $RANDOM
213expr $0 : '.*/osh$' && exit 99 # Disabled because of spec-runner.sh issue
214echo $RANDOM | egrep '[0-9]+'
215## status: 0
216## N-I dash status: 1
217
218#### $UID and $EUID
219# These are both bash-specific.
220set -o errexit
221echo $UID | egrep -o '[0-9]+' >/dev/null
222echo $EUID | egrep -o '[0-9]+' >/dev/null
223echo status=$?
224## stdout: status=0
225## N-I dash/mksh stdout-json: ""
226## N-I dash/mksh status: 1
227
228#### $OSTYPE is non-empty
229test -n "$OSTYPE"
230echo status=$?
231## STDOUT:
232status=0
233## END
234## N-I dash/mksh STDOUT:
235status=1
236## END
237
238#### $HOSTNAME
239test "$HOSTNAME" = "$(hostname)"
240echo status=$?
241## STDOUT:
242status=0
243## END
244## N-I dash/mksh/zsh STDOUT:
245status=1
246## END
247
248#### $LINENO is the current line, not line of function call
249echo $LINENO # first line
250g() {
251 argv.py $LINENO # line 3
252}
253f() {
254 argv.py $LINENO # line 6
255 g
256 argv.py $LINENO # line 8
257}
258f
259## STDOUT:
2601
261['6']
262['3']
263['8']
264## END
265## BUG zsh STDOUT:
2661
267['1']
268['1']
269['3']
270## END
271## BUG dash STDOUT:
2721
273['2']
274['2']
275['4']
276## END
277
278#### $LINENO in "bare" redirect arg (bug regression)
279filename=$TMP/bare3
280rm -f $filename
281> $TMP/bare$LINENO
282test -f $filename && echo written
283echo $LINENO
284## STDOUT:
285written
2865
287## END
288## BUG zsh STDOUT:
289## END
290
291#### $LINENO in redirect arg (bug regression)
292filename=$TMP/lineno_regression3
293rm -f $filename
294echo x > $TMP/lineno_regression$LINENO
295test -f $filename && echo written
296echo $LINENO
297## STDOUT:
298written
2995
300## END
301
302#### $LINENO in [[
303echo one
304[[ $LINENO -eq 2 ]] && echo OK
305## STDOUT:
306one
307OK
308## END
309## N-I dash status: 127
310## N-I dash stdout: one
311## N-I mksh status: 1
312## N-I mksh stdout: one
313
314#### $LINENO in ((
315echo one
316(( x = LINENO ))
317echo $x
318## STDOUT:
319one
3202
321## END
322## N-I dash stdout-json: "one\n\n"
323
324#### $LINENO in for loop
325# hm bash doesn't take into account the word break. That's OK; we won't either.
326echo one
327for x in \
328 $LINENO zzz; do
329 echo $x
330done
331## STDOUT:
332one
3332
334zzz
335## END
336## OK mksh STDOUT:
337one
3381
339zzz
340## END
341
342#### $LINENO in other for loops
343set -- a b c
344for x; do
345 echo $LINENO $x
346done
347## STDOUT:
3483 a
3493 b
3503 c
351## END
352
353#### $LINENO in for (( loop
354# This is a real edge case that I'm not sure we care about. We would have to
355# change the span ID inside the loop to make it really correct.
356echo one
357for (( i = 0; i < $LINENO; i++ )); do
358 echo $i
359done
360## STDOUT:
361one
3620
3631
364## END
365## N-I dash stdout: one
366## N-I dash status: 2
367## BUG mksh stdout: one
368## BUG mksh status: 1
369
370#### $LINENO for assignment
371a1=$LINENO a2=$LINENO
372b1=$LINENO b2=$LINENO
373echo $a1 $a2
374echo $b1 $b2
375## STDOUT:
3761 1
3772 2
378## END
379
380#### $LINENO in case
381case $LINENO in
382 1) echo 'got line 1' ;;
383 *) echo line=$LINENO
384esac
385## STDOUT:
386got line 1
387## END
388## BUG mksh STDOUT:
389line=3
390## END
391
392#### $_ with simple command and evaluation
393
394name=world
395echo "hi $name"
396echo "$_"
397## STDOUT:
398hi world
399hi world
400## END
401## N-I dash/mksh STDOUT:
402hi world
403
404## END
405
406#### $_ and ${_}
407case $SH in (dash|mksh) exit ;; esac
408
409_var=value
410
411: 42
412echo $_ $_var ${_}var
413
414: 'foo'"bar"
415echo $_
416
417## STDOUT:
41842 value 42var
419foobar
420## END
421## N-I dash/mksh stdout-json: ""
422
423#### $_ with word splitting
424case $SH in (dash|mksh) exit ;; esac
425
426setopt shwordsplit # for ZSH
427
428x='with spaces'
429: $x
430echo $_
431
432## STDOUT:
433spaces
434## END
435## N-I dash/mksh stdout-json: ""
436
437#### $_ with pipeline and subshell
438case $SH in (dash|mksh) exit ;; esac
439
440shopt -s lastpipe
441
442seq 3 | echo last=$_
443
444echo pipeline=$_
445
446( echo subshell=$_ )
447echo done=$_
448
449## STDOUT:
450last=
451pipeline=last=
452subshell=pipeline=last=
453done=pipeline=last=
454## END
455
456# very weird semantics for zsh!
457## OK zsh STDOUT:
458last=3
459pipeline=last=3
460subshell=
461done=
462## END
463
464## N-I dash/mksh stdout-json: ""
465
466
467#### $_ with && and ||
468case $SH in (dash|mksh) exit ;; esac
469
470echo hi && echo last=$_
471echo and=$_
472
473echo hi || echo last=$_
474echo or=$_
475
476## STDOUT:
477hi
478last=hi
479and=last=hi
480hi
481or=hi
482## END
483
484## N-I dash/mksh stdout-json: ""
485
486#### $_ is not reset with (( and [[
487
488# bash is inconsistent because it does it for pipelines and assignments, but
489# not (( and [[
490
491case $SH in (dash|mksh) exit ;; esac
492
493echo simple
494(( a = 2 + 3 ))
495echo "(( $_"
496
497[[ a == *.py ]]
498echo "[[ $_"
499
500## STDOUT:
501simple
502(( simple
503[[ (( simple
504## END
505
506## N-I dash/mksh stdout-json: ""
507
508
509#### $_ with assignments, arrays, etc.
510case $SH in (dash|mksh) exit ;; esac
511
512: foo
513echo "colon [$_]"
514
515s=bar
516echo "bare assign [$_]"
517
518# zsh uses declare; bash uses s=bar
519declare s=bar
520echo "declare [$_]"
521
522# zsh remains s:declare, bash resets it
523a=(1 2)
524echo "array [$_]"
525
526# zsh sets it to declare, bash uses the LHS a
527declare a=(1 2)
528echo "declare array [$_]"
529
530declare -g d=(1 2)
531echo "declare flag [$_]"
532
533## STDOUT:
534colon [foo]
535bare assign []
536declare [s=bar]
537array []
538declare array [a]
539declare flag [d]
540## END
541
542## OK zsh STDOUT:
543colon [foo]
544bare assign []
545declare [declare]
546array [declare [declare]]
547declare array [declare]
548declare flag [-g]
549## END
550
551## OK osh STDOUT:
552colon [foo]
553bare assign [colon [foo]]
554declare [bare assign [colon [foo]]]
555array [declare [bare assign [colon [foo]]]]
556declare array [array [declare [bare assign [colon [foo]]]]]
557declare flag [declare array [array [declare [bare assign [colon [foo]]]]]]
558## END
559
560## N-I dash/mksh stdout-json: ""
561
562#### $_ with loop
563
564case $SH in (dash|mksh) exit ;; esac
565
566# zsh resets it when in a loop
567
568echo init
569echo begin=$_
570for x in 1 2 3; do
571 echo prev=$_
572done
573
574## STDOUT:
575init
576begin=init
577prev=begin=init
578prev=prev=begin=init
579prev=prev=prev=begin=init
580## END
581
582## OK zsh STDOUT:
583init
584begin=init
585prev=
586prev=prev=
587prev=prev=prev=
588## END
589## N-I dash/mksh stdout-json: ""
590
591
592#### $_ is not undefined on first use
593set -e
594
595x=$($SH -u -c 'echo prev=$_')
596echo status=$?
597
598# bash and mksh set $_ to $0 at first; zsh is empty
599#echo "$x"
600
601## STDOUT:
602status=0
603## END
604
605## N-I dash status: 2
606## N-I dash stdout-json: ""
607
608#### BASH_VERSION / OILS_VERSION
609case $SH in
610 bash*)
611 # BASH_VERSION=zz
612
613 echo $BASH_VERSION | egrep -o '4\.4\.0' > /dev/null
614 echo matched=$?
615 ;;
616 *osh)
617 # note: version string is mutable like in bash. I guess that's useful for
618 # testing? We might want a strict mode to eliminate that?
619
620 echo $OILS_VERSION | egrep -o '[0-9]+\.[0-9]+\.' > /dev/null
621 echo matched=$?
622 ;;
623 *)
624 echo 'no version'
625 ;;
626esac
627## STDOUT:
628matched=0
629## END
630## N-I dash/mksh/zsh STDOUT:
631no version
632## END
633
634#### $SECONDS
635
636# should be zero seconds
637echo seconds=$SECONDS
638
639## status: 0
640## STDOUT:
641seconds=0
642## END
643## N-I dash STDOUT:
644seconds=
645## END