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