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