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