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-json: "one\n\n"
467
468 #### $LINENO in for loop
469 # hm bash doesn't take into account the word break. That's OK; we won't either.
470 echo one
471 for x in \
472 $LINENO zzz; do
473 echo $x
474 done
475 ## STDOUT:
476 one
477 2
478 zzz
479 ## END
480 ## OK mksh STDOUT:
481 one
482 1
483 zzz
484 ## END
485
486 #### $LINENO in other for loops
487 set -- a b c
488 for x; do
489 echo $LINENO $x
490 done
491 ## STDOUT:
492 3 a
493 3 b
494 3 c
495 ## END
496
497 #### $LINENO in for (( loop
498 # This is a real edge case that I'm not sure we care about. We would have to
499 # change the span ID inside the loop to make it really correct.
500 echo one
501 for (( i = 0; i < $LINENO; i++ )); do
502 echo $i
503 done
504 ## STDOUT:
505 one
506 0
507 1
508 ## END
509 ## N-I dash stdout: one
510 ## N-I dash status: 2
511 ## BUG mksh stdout: one
512 ## BUG mksh status: 1
513
514 #### $LINENO for assignment
515 a1=$LINENO a2=$LINENO
516 b1=$LINENO b2=$LINENO
517 echo $a1 $a2
518 echo $b1 $b2
519 ## STDOUT:
520 1 1
521 2 2
522 ## END
523
524 #### $LINENO in case
525 case $LINENO in
526 1) echo 'got line 1' ;;
527 *) echo line=$LINENO
528 esac
529 ## STDOUT:
530 got line 1
531 ## END
532 ## BUG mksh STDOUT:
533 line=3
534 ## END
535
536 #### $_ with simple command and evaluation
537
538 name=world
539 echo "hi $name"
540 echo "$_"
541 ## STDOUT:
542 hi world
543 hi world
544 ## END
545 ## N-I dash/mksh STDOUT:
546 hi world
547
548 ## END
549
550 #### $_ and ${_}
551 case $SH in (dash|mksh) exit ;; esac
552
553 _var=value
554
555 : 42
556 echo $_ $_var ${_}var
557
558 : 'foo'"bar"
559 echo $_
560
561 ## STDOUT:
562 42 value 42var
563 foobar
564 ## END
565 ## N-I dash/mksh stdout-json: ""
566
567 #### $_ with word splitting
568 case $SH in (dash|mksh) exit ;; esac
569
570 setopt shwordsplit # for ZSH
571
572 x='with spaces'
573 : $x
574 echo $_
575
576 ## STDOUT:
577 spaces
578 ## END
579 ## N-I dash/mksh stdout-json: ""
580
581 #### $_ with pipeline and subshell
582 case $SH in (dash|mksh) exit ;; esac
583
584 shopt -s lastpipe
585
586 seq 3 | echo last=$_
587
588 echo pipeline=$_
589
590 ( echo subshell=$_ )
591 echo done=$_
592
593 ## STDOUT:
594 last=
595 pipeline=last=
596 subshell=pipeline=last=
597 done=pipeline=last=
598 ## END
599
600 # very weird semantics for zsh!
601 ## OK zsh STDOUT:
602 last=3
603 pipeline=last=3
604 subshell=
605 done=
606 ## END
607
608 ## N-I dash/mksh stdout-json: ""
609
610
611 #### $_ with && and ||
612 case $SH in (dash|mksh) exit ;; esac
613
614 echo hi && echo last=$_
615 echo and=$_
616
617 echo hi || echo last=$_
618 echo or=$_
619
620 ## STDOUT:
621 hi
622 last=hi
623 and=last=hi
624 hi
625 or=hi
626 ## END
627
628 ## N-I dash/mksh stdout-json: ""
629
630 #### $_ is not reset with (( and [[
631
632 # bash is inconsistent because it does it for pipelines and assignments, but
633 # not (( and [[
634
635 case $SH in (dash|mksh) exit ;; esac
636
637 echo simple
638 (( a = 2 + 3 ))
639 echo "(( $_"
640
641 [[ a == *.py ]]
642 echo "[[ $_"
643
644 ## STDOUT:
645 simple
646 (( simple
647 [[ (( simple
648 ## END
649
650 ## N-I dash/mksh stdout-json: ""
651
652
653 #### $_ with assignments, arrays, etc.
654 case $SH in (dash|mksh) exit ;; esac
655
656 : foo
657 echo "colon [$_]"
658
659 s=bar
660 echo "bare assign [$_]"
661
662 # zsh uses declare; bash uses s=bar
663 declare s=bar
664 echo "declare [$_]"
665
666 # zsh remains s:declare, bash resets it
667 a=(1 2)
668 echo "array [$_]"
669
670 # zsh sets it to declare, bash uses the LHS a
671 declare a=(1 2)
672 echo "declare array [$_]"
673
674 declare -g d=(1 2)
675 echo "declare flag [$_]"
676
677 ## STDOUT:
678 colon [foo]
679 bare assign []
680 declare [s=bar]
681 array []
682 declare array [a]
683 declare flag [d]
684 ## END
685
686 ## OK zsh STDOUT:
687 colon [foo]
688 bare assign []
689 declare [declare]
690 array [declare [declare]]
691 declare array [declare]
692 declare flag [-g]
693 ## END
694
695 ## OK osh STDOUT:
696 colon [foo]
697 bare assign [colon [foo]]
698 declare [bare assign [colon [foo]]]
699 array [declare [bare assign [colon [foo]]]]
700 declare array [array [declare [bare assign [colon [foo]]]]]
701 declare flag [declare array [array [declare [bare assign [colon [foo]]]]]]
702 ## END
703
704 ## N-I dash/mksh stdout-json: ""
705
706 #### $_ with loop
707
708 case $SH in (dash|mksh) exit ;; esac
709
710 # zsh resets it when in a loop
711
712 echo init
713 echo begin=$_
714 for x in 1 2 3; do
715 echo prev=$_
716 done
717
718 ## STDOUT:
719 init
720 begin=init
721 prev=begin=init
722 prev=prev=begin=init
723 prev=prev=prev=begin=init
724 ## END
725
726 ## OK zsh STDOUT:
727 init
728 begin=init
729 prev=
730 prev=prev=
731 prev=prev=prev=
732 ## END
733 ## N-I dash/mksh stdout-json: ""
734
735
736 #### $_ is not undefined on first use
737 set -e
738
739 x=$($SH -u -c 'echo prev=$_')
740 echo status=$?
741
742 # bash and mksh set $_ to $0 at first; zsh is empty
743 #echo "$x"
744
745 ## STDOUT:
746 status=0
747 ## END
748
749 ## N-I dash status: 2
750 ## N-I dash stdout-json: ""
751
752 #### BASH_VERSION / OILS_VERSION
753 case $SH in
754 bash*)
755 # BASH_VERSION=zz
756
757 echo $BASH_VERSION | egrep -o '4\.4\.0' > /dev/null
758 echo matched=$?
759 ;;
760 *osh)
761 # note: version string is mutable like in bash. I guess that's useful for
762 # testing? We might want a strict mode to eliminate that?
763
764 echo $OILS_VERSION | egrep -o '[0-9]+\.[0-9]+\.' > /dev/null
765 echo matched=$?
766 ;;
767 *)
768 echo 'no version'
769 ;;
770 esac
771 ## STDOUT:
772 matched=0
773 ## END
774 ## N-I dash/mksh/zsh STDOUT:
775 no version
776 ## END
777
778 #### $SECONDS
779
780 # should be zero seconds
781 echo seconds=$SECONDS
782
783 ## status: 0
784 ## STDOUT:
785 seconds=0
786 ## END
787 ## N-I dash STDOUT:
788 seconds=
789 ## END
790