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 echo $0 | grep -o 'sh'
233 echo $1 $2
234 }
235 fun a b
236
237 ## STDOUT:
238 sh
239 a b
240 ## END
241 ## BUG zsh STDOUT:
242 a b
243 ## END
244
245 #### $?
246 echo $? # starts out as 0
247 sh -c 'exit 33'
248 echo $?
249 ## STDOUT:
250 0
251 33
252 ## END
253 ## status: 0
254
255 #### $#
256 set -- 1 2 3 4
257 echo $#
258 ## stdout: 4
259 ## status: 0
260
261 #### $$ looks like a PID
262 # Just test that it has decimal digits
263 echo $$ | egrep '[0-9]+'
264 ## status: 0
265
266 #### $$ doesn't change with subshell or command sub
267 # Just test that it has decimal digits
268 set -o errexit
269 die() {
270 echo 1>&2 "$@"; exit 1
271 }
272 parent=$$
273 test -n "$parent" || die "empty PID in parent"
274 ( child=$$
275 test -n "$child" || die "empty PID in subshell"
276 test "$parent" = "$child" || die "should be equal: $parent != $child"
277 echo 'subshell OK'
278 )
279 echo $( child=$$
280 test -n "$child" || die "empty PID in command sub"
281 test "$parent" = "$child" || die "should be equal: $parent != $child"
282 echo 'command sub OK'
283 )
284 exit 3 # make sure we got here
285 ## status: 3
286 ## STDOUT:
287 subshell OK
288 command sub OK
289 ## END
290
291 #### $BASHPID DOES change with subshell and command sub
292 set -o errexit
293 die() {
294 echo 1>&2 "$@"; exit 1
295 }
296 parent=$BASHPID
297 test -n "$parent" || die "empty BASHPID in parent"
298 ( child=$BASHPID
299 test -n "$child" || die "empty BASHPID in subshell"
300 test "$parent" != "$child" || die "should not be equal: $parent = $child"
301 echo 'subshell OK'
302 )
303 echo $( child=$BASHPID
304 test -n "$child" || die "empty BASHPID in command sub"
305 test "$parent" != "$child" ||
306 die "should not be equal: $parent = $child"
307 echo 'command sub OK'
308 )
309 exit 3 # make sure we got here
310
311 # mksh also implements BASHPID!
312
313 ## status: 3
314 ## STDOUT:
315 subshell OK
316 command sub OK
317 ## END
318 ## N-I dash/zsh status: 1
319 ## N-I dash/zsh stdout-json: ""
320
321 #### Background PID $! looks like a PID
322 sleep 0.01 &
323 pid=$!
324 wait
325 echo $pid | egrep '[0-9]+' >/dev/null
326 echo status=$?
327 ## stdout: status=0
328
329 #### $PPID
330 echo $PPID | egrep '[0-9]+'
331 ## status: 0
332
333 # NOTE: There is also $BASHPID
334
335 #### $PIPESTATUS
336 echo hi | sh -c 'cat; exit 33' | wc -l >/dev/null
337 argv.py "${PIPESTATUS[@]}"
338 ## status: 0
339 ## STDOUT:
340 ['0', '33', '0']
341 ## END
342 ## N-I dash stdout-json: ""
343 ## N-I dash status: 2
344 ## N-I zsh STDOUT:
345 ['']
346 ## END
347
348 #### $RANDOM
349 expr $0 : '.*/osh$' && exit 99 # Disabled because of spec-runner.sh issue
350 echo $RANDOM | egrep '[0-9]+'
351 ## status: 0
352 ## N-I dash status: 1
353
354 #### $UID and $EUID
355 # These are both bash-specific.
356 set -o errexit
357 echo $UID | egrep -o '[0-9]+' >/dev/null
358 echo $EUID | egrep -o '[0-9]+' >/dev/null
359 echo status=$?
360 ## stdout: status=0
361 ## N-I dash/mksh stdout-json: ""
362 ## N-I dash/mksh status: 1
363
364 #### $OSTYPE is non-empty
365 test -n "$OSTYPE"
366 echo status=$?
367 ## STDOUT:
368 status=0
369 ## END
370 ## N-I dash/mksh STDOUT:
371 status=1
372 ## END
373
374 #### $HOSTNAME
375 test "$HOSTNAME" = "$(hostname)"
376 echo status=$?
377 ## STDOUT:
378 status=0
379 ## END
380 ## N-I dash/mksh/zsh STDOUT:
381 status=1
382 ## END
383
384 #### $LINENO is the current line, not line of function call
385 echo $LINENO # first line
386 g() {
387 argv.py $LINENO # line 3
388 }
389 f() {
390 argv.py $LINENO # line 6
391 g
392 argv.py $LINENO # line 8
393 }
394 f
395 ## STDOUT:
396 1
397 ['6']
398 ['3']
399 ['8']
400 ## END
401 ## BUG zsh STDOUT:
402 1
403 ['1']
404 ['1']
405 ['3']
406 ## END
407 ## BUG dash STDOUT:
408 1
409 ['2']
410 ['2']
411 ['4']
412 ## END
413
414 #### $LINENO in "bare" redirect arg (bug regression)
415 filename=$TMP/bare3
416 rm -f $filename
417 > $TMP/bare$LINENO
418 test -f $filename && echo written
419 echo $LINENO
420 ## STDOUT:
421 written
422 5
423 ## END
424 ## BUG zsh STDOUT:
425 ## END
426
427 #### $LINENO in redirect arg (bug regression)
428 filename=$TMP/lineno_regression3
429 rm -f $filename
430 echo x > $TMP/lineno_regression$LINENO
431 test -f $filename && echo written
432 echo $LINENO
433 ## STDOUT:
434 written
435 5
436 ## END
437
438 #### $LINENO in [[
439 echo one
440 [[ $LINENO -eq 2 ]] && echo OK
441 ## STDOUT:
442 one
443 OK
444 ## END
445 ## N-I dash status: 127
446 ## N-I dash stdout: one
447 ## N-I mksh status: 1
448 ## N-I mksh stdout: one
449
450 #### $LINENO in ((
451 echo one
452 (( x = LINENO ))
453 echo $x
454 ## STDOUT:
455 one
456 2
457 ## END
458 ## N-I dash stdout-json: "one\n\n"
459
460 #### $LINENO in for loop
461 # hm bash doesn't take into account the word break. That's OK; we won't either.
462 echo one
463 for x in \
464 $LINENO zzz; do
465 echo $x
466 done
467 ## STDOUT:
468 one
469 2
470 zzz
471 ## END
472 ## OK mksh STDOUT:
473 one
474 1
475 zzz
476 ## END
477
478 #### $LINENO in other for loops
479 set -- a b c
480 for x; do
481 echo $LINENO $x
482 done
483 ## STDOUT:
484 3 a
485 3 b
486 3 c
487 ## END
488
489 #### $LINENO in for (( loop
490 # This is a real edge case that I'm not sure we care about. We would have to
491 # change the span ID inside the loop to make it really correct.
492 echo one
493 for (( i = 0; i < $LINENO; i++ )); do
494 echo $i
495 done
496 ## STDOUT:
497 one
498 0
499 1
500 ## END
501 ## N-I dash stdout: one
502 ## N-I dash status: 2
503 ## BUG mksh stdout: one
504 ## BUG mksh status: 1
505
506 #### $LINENO for assignment
507 a1=$LINENO a2=$LINENO
508 b1=$LINENO b2=$LINENO
509 echo $a1 $a2
510 echo $b1 $b2
511 ## STDOUT:
512 1 1
513 2 2
514 ## END
515
516 #### $LINENO in case
517 case $LINENO in
518 1) echo 'got line 1' ;;
519 *) echo line=$LINENO
520 esac
521 ## STDOUT:
522 got line 1
523 ## END
524 ## BUG mksh STDOUT:
525 line=3
526 ## END
527
528 #### $_ with simple command and evaluation
529
530 name=world
531 echo "hi $name"
532 echo "$_"
533 ## STDOUT:
534 hi world
535 hi world
536 ## END
537 ## N-I dash/mksh STDOUT:
538 hi world
539
540 ## END
541
542 #### $_ and ${_}
543 case $SH in (dash|mksh) exit ;; esac
544
545 _var=value
546
547 : 42
548 echo $_ $_var ${_}var
549
550 : 'foo'"bar"
551 echo $_
552
553 ## STDOUT:
554 42 value 42var
555 foobar
556 ## END
557 ## N-I dash/mksh stdout-json: ""
558
559 #### $_ with word splitting
560 case $SH in (dash|mksh) exit ;; esac
561
562 setopt shwordsplit # for ZSH
563
564 x='with spaces'
565 : $x
566 echo $_
567
568 ## STDOUT:
569 spaces
570 ## END
571 ## N-I dash/mksh stdout-json: ""
572
573 #### $_ with pipeline and subshell
574 case $SH in (dash|mksh) exit ;; esac
575
576 shopt -s lastpipe
577
578 seq 3 | echo last=$_
579
580 echo pipeline=$_
581
582 ( echo subshell=$_ )
583 echo done=$_
584
585 ## STDOUT:
586 last=
587 pipeline=last=
588 subshell=pipeline=last=
589 done=pipeline=last=
590 ## END
591
592 # very weird semantics for zsh!
593 ## OK zsh STDOUT:
594 last=3
595 pipeline=last=3
596 subshell=
597 done=
598 ## END
599
600 ## N-I dash/mksh stdout-json: ""
601
602
603 #### $_ with && and ||
604 case $SH in (dash|mksh) exit ;; esac
605
606 echo hi && echo last=$_
607 echo and=$_
608
609 echo hi || echo last=$_
610 echo or=$_
611
612 ## STDOUT:
613 hi
614 last=hi
615 and=last=hi
616 hi
617 or=hi
618 ## END
619
620 ## N-I dash/mksh stdout-json: ""
621
622 #### $_ is not reset with (( and [[
623
624 # bash is inconsistent because it does it for pipelines and assignments, but
625 # not (( and [[
626
627 case $SH in (dash|mksh) exit ;; esac
628
629 echo simple
630 (( a = 2 + 3 ))
631 echo "(( $_"
632
633 [[ a == *.py ]]
634 echo "[[ $_"
635
636 ## STDOUT:
637 simple
638 (( simple
639 [[ (( simple
640 ## END
641
642 ## N-I dash/mksh stdout-json: ""
643
644
645 #### $_ with assignments, arrays, etc.
646 case $SH in (dash|mksh) exit ;; esac
647
648 : foo
649 echo "colon [$_]"
650
651 s=bar
652 echo "bare assign [$_]"
653
654 # zsh uses declare; bash uses s=bar
655 declare s=bar
656 echo "declare [$_]"
657
658 # zsh remains s:declare, bash resets it
659 a=(1 2)
660 echo "array [$_]"
661
662 # zsh sets it to declare, bash uses the LHS a
663 declare a=(1 2)
664 echo "declare array [$_]"
665
666 declare -g d=(1 2)
667 echo "declare flag [$_]"
668
669 ## STDOUT:
670 colon [foo]
671 bare assign []
672 declare [s=bar]
673 array []
674 declare array [a]
675 declare flag [d]
676 ## END
677
678 ## OK zsh STDOUT:
679 colon [foo]
680 bare assign []
681 declare [declare]
682 array [declare [declare]]
683 declare array [declare]
684 declare flag [-g]
685 ## END
686
687 ## OK osh STDOUT:
688 colon [foo]
689 bare assign [colon [foo]]
690 declare [bare assign [colon [foo]]]
691 array [declare [bare assign [colon [foo]]]]
692 declare array [array [declare [bare assign [colon [foo]]]]]
693 declare flag [declare array [array [declare [bare assign [colon [foo]]]]]]
694 ## END
695
696 ## N-I dash/mksh stdout-json: ""
697
698 #### $_ with loop
699
700 case $SH in (dash|mksh) exit ;; esac
701
702 # zsh resets it when in a loop
703
704 echo init
705 echo begin=$_
706 for x in 1 2 3; do
707 echo prev=$_
708 done
709
710 ## STDOUT:
711 init
712 begin=init
713 prev=begin=init
714 prev=prev=begin=init
715 prev=prev=prev=begin=init
716 ## END
717
718 ## OK zsh STDOUT:
719 init
720 begin=init
721 prev=
722 prev=prev=
723 prev=prev=prev=
724 ## END
725 ## N-I dash/mksh stdout-json: ""
726
727
728 #### $_ is not undefined on first use
729 set -e
730
731 x=$($SH -u -c 'echo prev=$_')
732 echo status=$?
733
734 # bash and mksh set $_ to $0 at first; zsh is empty
735 #echo "$x"
736
737 ## STDOUT:
738 status=0
739 ## END
740
741 ## N-I dash status: 2
742 ## N-I dash stdout-json: ""
743
744 #### BASH_VERSION / OILS_VERSION
745 case $SH in
746 bash*)
747 # BASH_VERSION=zz
748
749 echo $BASH_VERSION | egrep -o '4\.4\.0' > /dev/null
750 echo matched=$?
751 ;;
752 *osh)
753 # note: version string is mutable like in bash. I guess that's useful for
754 # testing? We might want a strict mode to eliminate that?
755
756 echo $OILS_VERSION | egrep -o '[0-9]+\.[0-9]+\.' > /dev/null
757 echo matched=$?
758 ;;
759 *)
760 echo 'no version'
761 ;;
762 esac
763 ## STDOUT:
764 matched=0
765 ## END
766 ## N-I dash/mksh/zsh STDOUT:
767 no version
768 ## END
769
770 #### $SECONDS
771
772 # should be zero seconds
773 echo seconds=$SECONDS
774
775 ## status: 0
776 ## STDOUT:
777 seconds=0
778 ## END
779 ## N-I dash STDOUT:
780 seconds=
781 ## END
782