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