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