1 #!/usr/bin/env bash
2 #
3 # NOTE:
4 # - $! is tested in background.test.sh
5 # - $- is tested in sh-options
6 #
7 # TODO: It would be nice to make a table, like:
8 #
9 # $$ $BASHPID $PPID $SHLVL $BASH_SUBSHELL
10 # X
11 # (Subshell, Command Sub, Pipeline, Spawn $0)
12 #
13 # And see whether the variable changed.
14
15 #### $PWD is set
16 # Just test that it has a slash for now.
17 echo $PWD | grep /
18 ## status: 0
19
20 #### $PWD is not only set, but exported
21 env | grep PWD
22 ## status: 0
23 ## BUG mksh status: 1
24
25 #### $HOME is NOT set
26 case $SH in *zsh) echo 'zsh sets HOME'; exit ;; esac
27
28 home=$(echo $HOME)
29 test "$home" = ""
30 echo status=$?
31
32 env | grep HOME
33 echo status=$?
34
35 # not in interactive shell either
36 $SH -i -c 'echo $HOME' | grep /
37 echo status=$?
38
39 ## STDOUT:
40 status=0
41 status=1
42 status=1
43 ## END
44 ## BUG zsh STDOUT:
45 zsh sets HOME
46 ## END
47
48
49 #### $1 .. $9 are scoped, while $0 is not
50 fun() { echo $0 $1 $2 | sed -e 's/.*sh/sh/'; }
51 fun a b
52 ## stdout: sh a b
53 ## BUG zsh stdout: fun a b
54
55 #### $?
56 echo $? # starts out as 0
57 sh -c 'exit 33'
58 echo $?
59 ## STDOUT:
60 0
61 33
62 ## END
63 ## status: 0
64
65 #### $#
66 set -- 1 2 3 4
67 echo $#
68 ## stdout: 4
69 ## status: 0
70
71 #### $_
72 # This is bash-specific.
73 echo hi
74 echo $_
75 ## stdout-json: "hi\nhi\n"
76 ## N-I dash/mksh stdout-json: "hi\n\n"
77
78 #### $$ looks like a PID
79 # Just test that it has decimal digits
80 echo $$ | egrep '[0-9]+'
81 ## status: 0
82
83 #### $$ doesn't change with subshell or command sub
84 # Just test that it has decimal digits
85 set -o errexit
86 die() {
87 echo 1>&2 "$@"; exit 1
88 }
89 parent=$$
90 test -n "$parent" || die "empty PID in parent"
91 ( child=$$
92 test -n "$child" || die "empty PID in subshell"
93 test "$parent" = "$child" || die "should be equal: $parent != $child"
94 echo 'subshell OK'
95 )
96 echo $( child=$$
97 test -n "$child" || die "empty PID in command sub"
98 test "$parent" = "$child" || die "should be equal: $parent != $child"
99 echo 'command sub OK'
100 )
101 exit 3 # make sure we got here
102 ## status: 3
103 ## STDOUT:
104 subshell OK
105 command sub OK
106 ## END
107
108 #### $BASHPID DOES change with subshell and command sub
109 set -o errexit
110 die() {
111 echo 1>&2 "$@"; exit 1
112 }
113 parent=$BASHPID
114 test -n "$parent" || die "empty BASHPID in parent"
115 ( child=$BASHPID
116 test -n "$child" || die "empty BASHPID in subshell"
117 test "$parent" != "$child" || die "should not be equal: $parent = $child"
118 echo 'subshell OK'
119 )
120 echo $( child=$BASHPID
121 test -n "$child" || die "empty BASHPID in command sub"
122 test "$parent" != "$child" ||
123 die "should not be equal: $parent = $child"
124 echo 'command sub OK'
125 )
126 exit 3 # make sure we got here
127
128 # mksh also implements BASHPID!
129
130 ## status: 3
131 ## STDOUT:
132 subshell OK
133 command sub OK
134 ## END
135 ## N-I dash/zsh status: 1
136 ## N-I dash/zsh stdout-json: ""
137
138 #### Background PID $! looks like a PID
139 sleep 0.01 &
140 pid=$!
141 wait
142 echo $pid | egrep '[0-9]+' >/dev/null
143 echo status=$?
144 ## stdout: status=0
145
146 #### $PPID
147 echo $PPID | egrep '[0-9]+'
148 ## status: 0
149
150 # NOTE: There is also $BASHPID
151
152 #### $PIPESTATUS
153 echo hi | sh -c 'cat; exit 33' | wc -l >/dev/null
154 argv.py "${PIPESTATUS[@]}"
155 ## status: 0
156 ## STDOUT:
157 ['0', '33', '0']
158 ## END
159 ## N-I dash stdout-json: ""
160 ## N-I dash status: 2
161 ## N-I zsh STDOUT:
162 ['']
163 ## END
164
165 #### $RANDOM
166 expr $0 : '.*/osh$' && exit 99 # Disabled because of spec-runner.sh issue
167 echo $RANDOM | egrep '[0-9]+'
168 ## status: 0
169 ## N-I dash status: 1
170
171 #### $UID and $EUID
172 # These are both bash-specific.
173 set -o errexit
174 echo $UID | egrep -o '[0-9]+' >/dev/null
175 echo $EUID | egrep -o '[0-9]+' >/dev/null
176 echo status=$?
177 ## stdout: status=0
178 ## N-I dash/mksh stdout-json: ""
179 ## N-I dash/mksh status: 1
180
181 #### $OSTYPE is non-empty
182 test -n "$OSTYPE"
183 echo status=$?
184 ## STDOUT:
185 status=0
186 ## END
187 ## N-I dash/mksh STDOUT:
188 status=1
189 ## END
190
191 #### $HOSTNAME
192 test "$HOSTNAME" = "$(hostname)"
193 echo status=$?
194 ## STDOUT:
195 status=0
196 ## END
197 ## N-I dash/mksh/zsh STDOUT:
198 status=1
199 ## END
200
201 #### $LINENO is the current line, not line of function call
202 echo $LINENO # first line
203 g() {
204 argv.py $LINENO # line 3
205 }
206 f() {
207 argv.py $LINENO # line 6
208 g
209 argv.py $LINENO # line 8
210 }
211 f
212 ## STDOUT:
213 1
214 ['6']
215 ['3']
216 ['8']
217 ## END
218 ## BUG zsh STDOUT:
219 1
220 ['1']
221 ['1']
222 ['3']
223 ## END
224 ## BUG dash STDOUT:
225 1
226 ['2']
227 ['2']
228 ['4']
229 ## END
230
231 #### $LINENO in "bare" redirect arg (bug regression)
232 filename=$TMP/bare3
233 rm -f $filename
234 > $TMP/bare$LINENO
235 test -f $filename && echo written
236 echo $LINENO
237 ## STDOUT:
238 written
239 5
240 ## END
241 ## BUG zsh STDOUT:
242 ## END
243
244 #### $LINENO in redirect arg (bug regression)
245 filename=$TMP/lineno_regression3
246 rm -f $filename
247 echo x > $TMP/lineno_regression$LINENO
248 test -f $filename && echo written
249 echo $LINENO
250 ## STDOUT:
251 written
252 5
253 ## END
254
255 #### $LINENO for [[
256 echo one
257 [[ $LINENO -eq 2 ]] && echo OK
258 ## STDOUT:
259 one
260 OK
261 ## END
262 ## N-I dash status: 127
263 ## N-I dash stdout: one
264 ## N-I mksh status: 1
265 ## N-I mksh stdout: one
266
267 #### $LINENO for ((
268 echo one
269 (( x = LINENO ))
270 echo $x
271 ## STDOUT:
272 one
273 2
274 ## END
275 ## N-I dash stdout-json: "one\n\n"
276
277 #### $LINENO in for loop
278 # hm bash doesn't take into account the word break. That's OK; we won't either.
279 echo one
280 for x in \
281 $LINENO zzz; do
282 echo $x
283 done
284 ## STDOUT:
285 one
286 2
287 zzz
288 ## END
289 ## OK mksh STDOUT:
290 one
291 1
292 zzz
293 ## END
294
295 #### $LINENO in other for loops
296 set -- a b c
297 for x; do
298 echo $LINENO $x
299 done
300 ## STDOUT:
301 3 a
302 3 b
303 3 c
304 ## END
305
306 #### $LINENO in for (( loop
307 # This is a real edge case that I'm not sure we care about. We would have to
308 # change the span ID inside the loop to make it really correct.
309 echo one
310 for (( i = 0; i < $LINENO; i++ )); do
311 echo $i
312 done
313 ## STDOUT:
314 one
315 0
316 1
317 ## END
318 ## N-I dash stdout: one
319 ## N-I dash status: 2
320 ## BUG mksh stdout: one
321 ## BUG mksh status: 1
322
323 #### $LINENO for assignment
324 a1=$LINENO a2=$LINENO
325 b1=$LINENO b2=$LINENO
326 echo $a1 $a2
327 echo $b1 $b2
328 ## STDOUT:
329 1 1
330 2 2
331 ## END
332
333
334 #### $_
335 : 1
336 echo $_
337 : 'foo'"bar"
338 echo $_
339 ## STDOUT:
340 1
341 foobar
342 ## END
343 ## N-I dash/mksh stdout-json: "\n\n"
344
345 #### $_ with assignments, arrays, etc.
346 case $SH in (dash|mksh) exit ;; esac
347
348 : foo
349 echo $_
350
351 s=bar
352 echo s:$_
353
354 # zsh uses declare; bash uses s=bar
355 declare s=bar
356 echo s:$_
357
358 # zsh remains s:declare, bash resets it
359 a=(1 2)
360 echo a:$_
361
362 # zsh sets it to declare, bash uses the LHS a
363 declare a=(1 2)
364 echo a:$_
365
366 declare -g a=(1 2)
367 echo flag:$_
368
369 ## STDOUT:
370 foo
371 s:
372 s:s=bar
373 a:
374 a:a
375 flag:a
376 ## END
377 ## OK zsh STDOUT:
378 foo
379 s:
380 s:declare
381 a:s:declare
382 a:declare
383 flag:-g
384 ## END
385 ## N-I dash/mksh stdout-json: ""
386
387 #### $_ undefined
388 set -e
389
390 # seems to be set to $0 at first, interesting.
391 x=$($SH -u -c 'echo $_')
392 test -n "$x"
393 echo nonempty=$?
394 ## STDOUT:
395 nonempty=0
396 ## END
397 ## OK zsh status: 1
398 ## OK zsh stdout-json: ""
399 ## N-I dash status: 2
400 ## N-I dash stdout-json: ""
401
402 #### BASH_VERSION / OIL_VERSION
403 case $SH in
404 (bash)
405 # BASH_VERSION=zz
406
407 echo $BASH_VERSION | egrep -o '4\.4\.0' > /dev/null
408 echo matched=$?
409 ;;
410 (*osh)
411 # note: version string is mutable like in bash. I guess that's useful for
412 # testing? We might want a strict mode to eliminate that?
413
414 echo $OIL_VERSION | egrep -o '[0-9]\.[0-9]\.' > /dev/null
415 echo matched=$?
416 ;;
417 (*)
418 echo 'no version'
419 ;;
420 esac
421 ## STDOUT:
422 matched=0
423 ## END
424 ## N-I dash/mksh/zsh STDOUT:
425 no version
426 ## END