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