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