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 -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 expr $0 : '.*/osh$' && exit 99 # Disabled because of spec-runner.sh issue
364 echo $RANDOM | egrep '[0-9]+'
365 ## status: 0
366 ## N-I dash status: 1
367
368 #### $UID and $EUID
369 # These are both bash-specific.
370 set -o errexit
371 echo $UID | egrep -o '[0-9]+' >/dev/null
372 echo $EUID | egrep -o '[0-9]+' >/dev/null
373 echo status=$?
374 ## stdout: status=0
375 ## N-I dash/mksh stdout-json: ""
376 ## N-I dash/mksh status: 1
377
378 #### $OSTYPE is non-empty
379 test -n "$OSTYPE"
380 echo status=$?
381 ## STDOUT:
382 status=0
383 ## END
384 ## N-I dash/mksh STDOUT:
385 status=1
386 ## END
387
388 #### $HOSTNAME
389 test "$HOSTNAME" = "$(hostname)"
390 echo status=$?
391 ## STDOUT:
392 status=0
393 ## END
394 ## N-I dash/mksh/zsh STDOUT:
395 status=1
396 ## END
397
398 #### $LINENO is the current line, not line of function call
399 echo $LINENO # first line
400 g() {
401 argv.py $LINENO # line 3
402 }
403 f() {
404 argv.py $LINENO # line 6
405 g
406 argv.py $LINENO # line 8
407 }
408 f
409 ## STDOUT:
410 1
411 ['6']
412 ['3']
413 ['8']
414 ## END
415 ## BUG zsh STDOUT:
416 1
417 ['1']
418 ['1']
419 ['3']
420 ## END
421 ## BUG dash STDOUT:
422 1
423 ['2']
424 ['2']
425 ['4']
426 ## END
427
428 #### $LINENO in "bare" redirect arg (bug regression)
429 filename=$TMP/bare3
430 rm -f $filename
431 > $TMP/bare$LINENO
432 test -f $filename && echo written
433 echo $LINENO
434 ## STDOUT:
435 written
436 5
437 ## END
438 ## BUG zsh STDOUT:
439 ## END
440
441 #### $LINENO in redirect arg (bug regression)
442 filename=$TMP/lineno_regression3
443 rm -f $filename
444 echo x > $TMP/lineno_regression$LINENO
445 test -f $filename && echo written
446 echo $LINENO
447 ## STDOUT:
448 written
449 5
450 ## END
451
452 #### $LINENO in [[
453 echo one
454 [[ $LINENO -eq 2 ]] && echo OK
455 ## STDOUT:
456 one
457 OK
458 ## END
459 ## N-I dash status: 127
460 ## N-I dash stdout: one
461 ## N-I mksh status: 1
462 ## N-I mksh stdout: one
463
464 #### $LINENO in ((
465 echo one
466 (( x = LINENO ))
467 echo $x
468 ## STDOUT:
469 one
470 2
471 ## END
472 ## N-I dash STDOUT:
473 one
474
475 ## END
476
477 #### $LINENO in for loop
478 # hm bash doesn't take into account the word break. That's OK; we won't either.
479 echo one
480 for x in \
481 $LINENO zzz; do
482 echo $x
483 done
484 ## STDOUT:
485 one
486 2
487 zzz
488 ## END
489 ## OK mksh STDOUT:
490 one
491 1
492 zzz
493 ## END
494
495 #### $LINENO in other for loops
496 set -- a b c
497 for x; do
498 echo $LINENO $x
499 done
500 ## STDOUT:
501 3 a
502 3 b
503 3 c
504 ## END
505
506 #### $LINENO in for (( loop
507 # This is a real edge case that I'm not sure we care about. We would have to
508 # change the span ID inside the loop to make it really correct.
509 echo one
510 for (( i = 0; i < $LINENO; i++ )); do
511 echo $i
512 done
513 ## STDOUT:
514 one
515 0
516 1
517 ## END
518 ## N-I dash stdout: one
519 ## N-I dash status: 2
520 ## BUG mksh stdout: one
521 ## BUG mksh status: 1
522
523 #### $LINENO for assignment
524 a1=$LINENO a2=$LINENO
525 b1=$LINENO b2=$LINENO
526 echo $a1 $a2
527 echo $b1 $b2
528 ## STDOUT:
529 1 1
530 2 2
531 ## END
532
533 #### $LINENO in case
534 case $LINENO in
535 1) echo 'got line 1' ;;
536 *) echo line=$LINENO
537 esac
538 ## STDOUT:
539 got line 1
540 ## END
541 ## BUG mksh STDOUT:
542 line=3
543 ## END
544
545 #### $_ with simple command and evaluation
546
547 name=world
548 echo "hi $name"
549 echo "$_"
550 ## STDOUT:
551 hi world
552 hi world
553 ## END
554 ## N-I dash/mksh STDOUT:
555 hi world
556
557 ## END
558
559 #### $_ and ${_}
560 case $SH in dash|mksh) exit ;; esac
561
562 _var=value
563
564 : 42
565 echo $_ $_var ${_}var
566
567 : 'foo'"bar"
568 echo $_
569
570 ## STDOUT:
571 42 value 42var
572 foobar
573 ## END
574 ## N-I dash/mksh stdout-json: ""
575
576 #### $_ with word splitting
577 case $SH in dash|mksh) exit ;; esac
578
579 setopt shwordsplit # for ZSH
580
581 x='with spaces'
582 : $x
583 echo $_
584
585 ## STDOUT:
586 spaces
587 ## END
588 ## N-I dash/mksh stdout-json: ""
589
590 #### $_ with pipeline and subshell
591 case $SH in dash|mksh) exit ;; esac
592
593 shopt -s lastpipe
594
595 seq 3 | echo last=$_
596
597 echo pipeline=$_
598
599 ( echo subshell=$_ )
600 echo done=$_
601
602 ## STDOUT:
603 last=
604 pipeline=last=
605 subshell=pipeline=last=
606 done=pipeline=last=
607 ## END
608
609 # very weird semantics for zsh!
610 ## OK zsh STDOUT:
611 last=3
612 pipeline=last=3
613 subshell=
614 done=
615 ## END
616
617 ## N-I dash/mksh stdout-json: ""
618
619
620 #### $_ with && and ||
621 case $SH in dash|mksh) exit ;; esac
622
623 echo hi && echo last=$_
624 echo and=$_
625
626 echo hi || echo last=$_
627 echo or=$_
628
629 ## STDOUT:
630 hi
631 last=hi
632 and=last=hi
633 hi
634 or=hi
635 ## END
636
637 ## N-I dash/mksh stdout-json: ""
638
639 #### $_ is not reset with (( and [[
640
641 # bash is inconsistent because it does it for pipelines and assignments, but
642 # not (( and [[
643
644 case $SH in dash|mksh) exit ;; esac
645
646 echo simple
647 (( a = 2 + 3 ))
648 echo "(( $_"
649
650 [[ a == *.py ]]
651 echo "[[ $_"
652
653 ## STDOUT:
654 simple
655 (( simple
656 [[ (( simple
657 ## END
658
659 ## N-I dash/mksh stdout-json: ""
660
661
662 #### $_ with assignments, arrays, etc.
663 case $SH in dash|mksh) exit ;; esac
664
665 : foo
666 echo "colon [$_]"
667
668 s=bar
669 echo "bare assign [$_]"
670
671 # zsh uses declare; bash uses s=bar
672 declare s=bar
673 echo "declare [$_]"
674
675 # zsh remains s:declare, bash resets it
676 a=(1 2)
677 echo "array [$_]"
678
679 # zsh sets it to declare, bash uses the LHS a
680 declare a=(1 2)
681 echo "declare array [$_]"
682
683 declare -g d=(1 2)
684 echo "declare flag [$_]"
685
686 ## STDOUT:
687 colon [foo]
688 bare assign []
689 declare [s=bar]
690 array []
691 declare array [a]
692 declare flag [d]
693 ## END
694
695 ## OK zsh STDOUT:
696 colon [foo]
697 bare assign []
698 declare [declare]
699 array [declare [declare]]
700 declare array [declare]
701 declare flag [-g]
702 ## END
703
704 ## OK osh STDOUT:
705 colon [foo]
706 bare assign [colon [foo]]
707 declare [bare assign [colon [foo]]]
708 array [declare [bare assign [colon [foo]]]]
709 declare array [array [declare [bare assign [colon [foo]]]]]
710 declare flag [declare array [array [declare [bare assign [colon [foo]]]]]]
711 ## END
712
713 ## N-I dash/mksh stdout-json: ""
714
715 #### $_ with loop
716
717 case $SH in dash|mksh) exit ;; esac
718
719 # zsh resets it when in a loop
720
721 echo init
722 echo begin=$_
723 for x in 1 2 3; do
724 echo prev=$_
725 done
726
727 ## STDOUT:
728 init
729 begin=init
730 prev=begin=init
731 prev=prev=begin=init
732 prev=prev=prev=begin=init
733 ## END
734
735 ## OK zsh STDOUT:
736 init
737 begin=init
738 prev=
739 prev=prev=
740 prev=prev=prev=
741 ## END
742 ## N-I dash/mksh stdout-json: ""
743
744
745 #### $_ is not undefined on first use
746 set -e
747
748 x=$($SH -u -c 'echo prev=$_')
749 echo status=$?
750
751 # bash and mksh set $_ to $0 at first; zsh is empty
752 #echo "$x"
753
754 ## STDOUT:
755 status=0
756 ## END
757
758 ## N-I dash status: 2
759 ## N-I dash stdout-json: ""
760
761 #### BASH_VERSION / OILS_VERSION
762 case $SH in
763 bash*)
764 # BASH_VERSION=zz
765
766 echo $BASH_VERSION | egrep -o '4\.4\.0' > /dev/null
767 echo matched=$?
768 ;;
769 *osh)
770 # note: version string is mutable like in bash. I guess that's useful for
771 # testing? We might want a strict mode to eliminate that?
772
773 echo $OILS_VERSION | egrep -o '[0-9]+\.[0-9]+\.' > /dev/null
774 echo matched=$?
775 ;;
776 *)
777 echo 'no version'
778 ;;
779 esac
780 ## STDOUT:
781 matched=0
782 ## END
783 ## N-I dash/mksh/zsh STDOUT:
784 no version
785 ## END
786
787 #### $SECONDS
788
789 # most likely 0 seconds, but in CI I've seen 1 second
790 echo $SECONDS | awk '/[0-9]+/ { print "ok" }'
791
792 ## status: 0
793 ## STDOUT:
794 ok
795 ## END
796 ## N-I dash STDOUT:
797 ## END