1 ## compare_shells: bash dash mksh zsh
2 ## oils_failures_allowed: 0
3
4 #### Lazy Evaluation of Alternative
5 i=0
6 x=x
7 echo ${x:-$((i++))}
8 echo $i
9 echo ${undefined:-$((i++))}
10 echo $i # i is one because the alternative was only evaluated once
11 ## status: 0
12 ## STDOUT:
13 x
14 0
15 0
16 1
17 ## END
18 ## N-I dash status: 2
19 ## N-I dash STDOUT:
20 x
21 0
22 ## END
23
24 #### Default value when empty
25 empty=''
26 echo ${empty:-is empty}
27 ## stdout: is empty
28
29 #### Default value when unset
30 echo ${unset-is unset}
31 ## stdout: is unset
32
33 #### Unquoted with array as default value
34 set -- '1 2' '3 4'
35 argv.py X${unset=x"$@"x}X
36 argv.py X${unset=x$@x}X # If you want OSH to split, write this
37 # osh
38 ## STDOUT:
39 ['Xx1', '2', '3', '4xX']
40 ['Xx1', '2', '3', '4xX']
41 ## END
42 ## OK osh STDOUT:
43 ['Xx1 2', '3 4xX']
44 ['Xx1', '2', '3', '4xX']
45 ## END
46 ## OK zsh STDOUT:
47 ['Xx1 2 3 4xX']
48 ['Xx1 2 3 4xX']
49 ## END
50
51 #### Quoted with array as default value
52 set -- '1 2' '3 4'
53 argv.py "X${unset=x"$@"x}X"
54 argv.py "X${unset=x$@x}X" # OSH is the same here
55 ## STDOUT:
56 ['Xx1 2 3 4xX']
57 ['Xx1 2 3 4xX']
58 ## END
59
60 # Bash 4.2..4.4 had a bug. This was fixed in Bash 5.0.
61 #
62 # ## BUG bash STDOUT:
63 # ['Xx1', '2', '3', '4xX']
64 # ['Xx1 2 3 4xX']
65 # ## END
66
67 ## OK osh STDOUT:
68 ['Xx1 2', '3 4xX']
69 ['Xx1 2 3 4xX']
70 ## END
71
72 #### Assign default with array
73 set -- '1 2' '3 4'
74 argv.py X${unset=x"$@"x}X
75 argv.py "$unset"
76 ## STDOUT:
77 ['Xx1', '2', '3', '4xX']
78 ['x1 2 3 4x']
79 ## END
80 ## OK osh STDOUT:
81 ['Xx1 2', '3 4xX']
82 ['x1 2 3 4x']
83 ## END
84 ## OK zsh STDOUT:
85 ['Xx1 2 3 4xX']
86 ['x1 2 3 4x']
87 ## END
88
89 #### Assign default value when empty
90 empty=''
91 ${empty:=is empty}
92 echo $empty
93 ## stdout: is empty
94
95 #### Assign default value when unset
96 ${unset=is unset}
97 echo $unset
98 ## stdout: is unset
99
100 #### ${v:+foo} Alternative value when empty
101 v=foo
102 empty=''
103 echo ${v:+v is not empty} ${empty:+is not empty}
104 ## stdout: v is not empty
105
106 #### ${v+foo} Alternative value when unset
107 v=foo
108 echo ${v+v is not unset} ${unset:+is not unset}
109 ## stdout: v is not unset
110
111 #### "${x+foo}" quoted (regression)
112 # Python's configure caught this
113 argv.py "${with_icc+set}" = set
114 ## STDOUT:
115 ['', '=', 'set']
116 ## END
117
118 #### ${s+foo} and ${s:+foo} when set -u
119 set -u
120 v=v
121 echo v=${v:+foo}
122 echo v=${v+foo}
123 unset v
124 echo v=${v:+foo}
125 echo v=${v+foo}
126 ## STDOUT:
127 v=foo
128 v=foo
129 v=
130 v=
131 ## END
132
133 #### "${array[@]} with set -u (bash is outlier)
134 case $SH in dash) exit ;; esac
135
136 set -u
137
138 typeset -a empty
139 empty=()
140
141 echo empty /"${empty[@]}"/
142 echo undefined /"${undefined[@]}"/
143
144 ## status: 1
145 ## STDOUT:
146 empty //
147 ## END
148
149 ## BUG bash status: 0
150 ## BUG bash STDOUT:
151 empty //
152 undefined //
153 ## END
154
155 # empty array is unset in mksh
156 ## BUG mksh status: 1
157 ## BUG mksh STDOUT:
158 ## END
159
160 ## N-I dash status: 0
161 ## N-I dash STDOUT:
162 ## END
163
164
165 #### "${undefined[@]+foo}" and "${undefined[@]:+foo}", with set -u
166 case $SH in dash) exit ;; esac
167
168 set -u
169
170 echo plus /"${array[@]+foo}"/
171 echo plus colon /"${array[@]:+foo}"/
172
173 ## STDOUT:
174 plus //
175 plus colon //
176 ## END
177
178 ## N-I dash STDOUT:
179 ## END
180
181 #### "${a[@]+foo}" and "${a[@]:+foo}" - operators are equivalent on arrays?
182
183 case $SH in dash) exit ;; esac
184
185 echo '+ ' /"${array[@]+foo}"/
186 echo '+:' /"${array[@]:+foo}"/
187 echo
188
189 typeset -a array
190 array=()
191
192 echo '+ ' /"${array[@]+foo}"/
193 echo '+:' /"${array[@]:+foo}"/
194 echo
195
196 array=('')
197
198 echo '+ ' /"${array[@]+foo}"/
199 echo '+:' /"${array[@]:+foo}"/
200 echo
201
202 array=(spam eggs)
203
204 echo '+ ' /"${array[@]+foo}"/
205 echo '+:' /"${array[@]:+foo}"/
206 echo
207
208
209 ## BUG mksh STDOUT:
210 + //
211 +: //
212
213 + //
214 +: //
215
216 + /foo/
217 +: //
218
219 + /foo/
220 +: /foo/
221
222 ## END
223
224 # Bash 2.0..4.4 has a bug that "${a[@]:-xxx}" produces an empty string. It
225 # seemed to consider a[@] and a[*] are non-empty when there is at least one
226 # element even if the element is empty. This was fixed in Bash 5.0.
227 #
228 # ## BUG bash STDOUT:
229 # + //
230 # +: //
231 #
232 # + //
233 # +: //
234 #
235 # + /foo/
236 # +: /foo/
237 #
238 # + /foo/
239 # +: /foo/
240 #
241 # ## END
242
243 ## BUG zsh STDOUT:
244 + //
245 +: //
246
247 + /foo/
248 +: //
249
250 + /foo/
251 +: /foo/
252
253 + /foo/
254 +: /foo/
255
256 ## END
257
258 ## N-I dash STDOUT:
259 ## END
260
261
262
263 #### Nix idiom ${!hooksSlice+"${!hooksSlice}"} - was workaround for obsolete bash 4.3 bug
264
265 case $SH in dash|mksh|zsh) exit ;; esac
266
267 # https://oilshell.zulipchat.com/#narrow/stream/307442-nix/topic/Replacing.20bash.20with.20osh.20in.20Nixpkgs.20stdenv
268
269 (argv.py ${!hooksSlice+"${!hooksSlice}"})
270
271 hooksSlice=x
272
273 argv.py ${!hooksSlice+"${!hooksSlice}"}
274
275 declare -a hookSlice=()
276
277 argv.py ${!hooksSlice+"${!hooksSlice}"}
278
279 foo=42
280 bar=43
281
282 declare -a hooksSlice=(foo bar spam eggs)
283
284 argv.py ${!hooksSlice+"${!hooksSlice}"}
285
286 ## STDOUT:
287 []
288 []
289 ['42']
290 ## END
291
292 # Bash 4.4 has a bug that ${!undef-} successfully generates an empty word.
293 #
294 # ## BUG bash STDOUT:
295 # []
296 # []
297 # []
298 # ['42']
299 # ## END
300
301 ## OK dash/mksh/zsh STDOUT:
302 ## END
303
304 #### ${v-foo} and ${v:-foo} when set -u
305 set -u
306 v=v
307 echo v=${v:-foo}
308 echo v=${v-foo}
309 unset v
310 echo v=${v:-foo}
311 echo v=${v-foo}
312 ## STDOUT:
313 v=v
314 v=v
315 v=foo
316 v=foo
317 ## END
318
319 #### array and - and +
320 case $SH in (dash) exit ;; esac
321
322 shopt -s compat_array # to refer to array as scalar
323
324 empty=()
325 a1=('')
326 a2=('' x)
327 a3=(3 4)
328 echo empty=${empty[@]-minus}
329 echo a1=${a1[@]-minus}
330 echo a1[0]=${a1[0]-minus}
331 echo a2=${a2[@]-minus}
332 echo a3=${a3[@]-minus}
333 echo ---
334
335 echo empty=${empty[@]+plus}
336 echo a1=${a1[@]+plus}
337 echo a1[0]=${a1[0]+plus}
338 echo a2=${a2[@]+plus}
339 echo a3=${a3[@]+plus}
340 echo ---
341
342 echo empty=${empty+plus}
343 echo a1=${a1+plus}
344 echo a2=${a2+plus}
345 echo a3=${a3+plus}
346 echo ---
347
348 # Test quoted arrays too
349 argv.py "${empty[@]-minus}"
350 argv.py "${empty[@]+plus}"
351 argv.py "${a1[@]-minus}"
352 argv.py "${a1[@]+plus}"
353 argv.py "${a1[0]-minus}"
354 argv.py "${a1[0]+plus}"
355 argv.py "${a2[@]-minus}"
356 argv.py "${a2[@]+plus}"
357 argv.py "${a3[@]-minus}"
358 argv.py "${a3[@]+plus}"
359
360 ## STDOUT:
361 empty=minus
362 a1=
363 a1[0]=
364 a2= x
365 a3=3 4
366 ---
367 empty=
368 a1=plus
369 a1[0]=plus
370 a2=plus
371 a3=plus
372 ---
373 empty=
374 a1=plus
375 a2=plus
376 a3=plus
377 ---
378 ['minus']
379 []
380 ['']
381 ['plus']
382 ['']
383 ['plus']
384 ['', 'x']
385 ['plus']
386 ['3', '4']
387 ['plus']
388 ## END
389 ## N-I dash stdout-json: ""
390 ## N-I zsh STDOUT:
391 empty=
392 a1=
393 ## END
394 ## N-I zsh status: 1
395
396 #### $@ (empty) and - and +
397 echo argv=${@-minus}
398 echo argv=${@+plus}
399 echo argv=${@:-minus}
400 echo argv=${@:+plus}
401 ## STDOUT:
402 argv=minus
403 argv=
404 argv=minus
405 argv=
406 ## END
407 ## BUG dash/zsh STDOUT:
408 argv=
409 argv=plus
410 argv=minus
411 argv=
412 ## END
413
414 #### $@ ("") and - and +
415 set -- ""
416 echo argv=${@-minus}
417 echo argv=${@+plus}
418 echo argv=${@:-minus}
419 echo argv=${@:+plus}
420 ## STDOUT:
421 argv=
422 argv=plus
423 argv=minus
424 argv=
425 ## END
426
427 # Zsh treats $@ as an array unlike Bash converting it to a string by joining it
428 # with a space.
429
430 ## OK zsh STDOUT:
431 argv=
432 argv=plus
433 argv=
434 argv=plus
435 ## END
436
437 #### $@ ("" "") and - and +
438 set -- "" ""
439 echo argv=${@-minus}
440 echo argv=${@+plus}
441 echo argv=${@:-minus}
442 echo argv=${@:+plus}
443 ## STDOUT:
444 argv=
445 argv=plus
446 argv=
447 argv=plus
448 ## END
449
450 #### $* ("" "") and - and + (IFS=)
451 set -- "" ""
452 IFS=
453 echo argv=${*-minus}
454 echo argv=${*+plus}
455 echo argv=${*:-minus}
456 echo argv=${*:+plus}
457 ## STDOUT:
458 argv=
459 argv=plus
460 argv=
461 argv=plus
462 ## END
463 ## BUG mksh STDOUT:
464 argv=
465 argv=plus
466 argv=minus
467 argv=
468 ## END
469
470 #### "$*" ("" "") and - and + (IFS=)
471 set -- "" ""
472 IFS=
473 echo "argv=${*-minus}"
474 echo "argv=${*+plus}"
475 echo "argv=${*:-minus}"
476 echo "argv=${*:+plus}"
477 ## STDOUT:
478 argv=
479 argv=plus
480 argv=minus
481 argv=
482 ## END
483
484 #### assoc array and - and +
485 case $SH in (dash|mksh) exit ;; esac
486
487 declare -A empty=()
488 declare -A assoc=(['k']=v)
489
490 echo empty=${empty[@]-minus}
491 echo empty=${empty[@]+plus}
492 echo assoc=${assoc[@]-minus}
493 echo assoc=${assoc[@]+plus}
494
495 echo ---
496 echo empty=${empty[@]:-minus}
497 echo empty=${empty[@]:+plus}
498 echo assoc=${assoc[@]:-minus}
499 echo assoc=${assoc[@]:+plus}
500 ## STDOUT:
501 empty=minus
502 empty=
503 assoc=v
504 assoc=plus
505 ---
506 empty=minus
507 empty=
508 assoc=v
509 assoc=plus
510 ## END
511
512 ## BUG zsh STDOUT:
513 empty=
514 empty=plus
515 assoc=minus
516 assoc=
517 ---
518 empty=minus
519 empty=
520 assoc=minus
521 assoc=
522 ## END
523
524 ## N-I dash/mksh STDOUT:
525 ## END
526
527
528 #### Error when empty
529 empty=''
530 echo ${empty:?'is em'pty} # test eval of error
531 echo should not get here
532 ## stdout-json: ""
533 ## status: 1
534 ## OK dash status: 2
535
536 #### Error when unset
537 echo ${unset?is empty}
538 echo should not get here
539 ## stdout-json: ""
540 ## status: 1
541 ## OK dash status: 2
542
543 #### Error when unset
544 v=foo
545 echo ${v+v is not unset} ${unset:+is not unset}
546 ## stdout: v is not unset
547
548 #### ${var=x} dynamic scope
549 f() { : "${hello:=x}"; echo $hello; }
550 f
551 echo hello=$hello
552
553 f() { hello=x; }
554 f
555 echo hello=$hello
556 ## STDOUT:
557 x
558 hello=x
559 hello=x
560 ## END
561
562 #### array ${arr[0]=x}
563 arr=()
564 echo ${#arr[@]}
565 : ${arr[0]=x}
566 echo ${#arr[@]}
567 ## STDOUT:
568 0
569 1
570 ## END
571 ## N-I dash status: 2
572 ## N-I dash stdout-json: ""
573 ## N-I zsh status: 1
574 ## N-I zsh STDOUT:
575 0
576 ## END
577
578 #### assoc array ${arr["k"]=x}
579 # note: this also works in zsh
580
581 declare -A arr=()
582 echo ${#arr[@]}
583 : ${arr['k']=x}
584 echo ${#arr[@]}
585 ## STDOUT:
586 0
587 1
588 ## END
589 ## N-I dash status: 2
590 ## N-I dash stdout-json: ""
591 ## N-I mksh status: 1
592 ## N-I mksh stdout-json: ""
593
594 #### "\z" as arg
595 echo "${undef-\$}"
596 echo "${undef-\(}"
597 echo "${undef-\z}"
598 echo "${undef-\"}"
599 echo "${undef-\`}"
600 echo "${undef-\\}"
601 ## STDOUT:
602 $
603 \(
604 \z
605 "
606 `
607 \
608 ## END
609 ## BUG yash STDOUT:
610 $
611 (
612 z
613 "
614 `
615 \
616 ## END
617 # Note: this line terminates the quoting by ` not to confuse the text editor.
618
619
620 #### "\e" as arg
621 echo "${undef-\e}"
622 ## STDOUT:
623 \e
624 ## END
625 ## BUG zsh/mksh stdout-repr: '\x1b\n'
626 ## BUG yash stdout: e
627
628
629 #### op-test for ${a} and ${a[0]}
630 case $SH in dash) exit ;; esac
631
632 test-hyphen() {
633 echo "a : '${a-no-colon}' '${a:-with-colon}'"
634 echo "a[0]: '${a[0]-no-colon}' '${a[0]:-with-colon}'"
635 }
636
637 a=()
638 test-hyphen
639 a=("")
640 test-hyphen
641 a=("" "")
642 test-hyphen
643 IFS=
644 test-hyphen
645
646 ## STDOUT:
647 a : 'no-colon' 'with-colon'
648 a[0]: 'no-colon' 'with-colon'
649 a : '' 'with-colon'
650 a[0]: '' 'with-colon'
651 a : '' 'with-colon'
652 a[0]: '' 'with-colon'
653 a : '' 'with-colon'
654 a[0]: '' 'with-colon'
655 ## END
656
657 # Zsh's ${a} and ${a[@]} implement something different from the other shells'.
658
659 ## OK zsh STDOUT:
660 a : '' 'with-colon'
661 a[0]: 'no-colon' 'with-colon'
662 a : '' 'with-colon'
663 a[0]: 'no-colon' 'with-colon'
664 a : ' ' ' '
665 a[0]: 'no-colon' 'with-colon'
666 a : '' 'with-colon'
667 a[0]: 'no-colon' 'with-colon'
668 ## END
669
670 ## N-I dash STDOUT:
671 ## END:
672
673
674 #### op-test for ${a[@]} and ${a[*]}
675 case $SH in dash) exit ;; esac
676
677 test-hyphen() {
678 echo "a[@]: '${a[@]-no-colon}' '${a[@]:-with-colon}'"
679 echo "a[*]: '${a[*]-no-colon}' '${a[*]:-with-colon}'"
680 }
681
682 a=()
683 test-hyphen
684 a=("")
685 test-hyphen
686 a=("" "")
687 test-hyphen
688 IFS=
689 test-hyphen
690
691 ## STDOUT:
692 a[@]: 'no-colon' 'with-colon'
693 a[*]: 'no-colon' 'with-colon'
694 a[@]: '' 'with-colon'
695 a[*]: '' 'with-colon'
696 a[@]: ' ' ' '
697 a[*]: ' ' ' '
698 a[@]: ' ' ' '
699 a[*]: '' 'with-colon'
700 ## END
701
702 # Bash 2.0..4.4 has a bug that "${a[@]:-xxx}" produces an empty string. It
703 # seemed to consider a[@] and a[*] are non-empty when there is at least one
704 # element even if the element is empty. This was fixed in Bash 5.0.
705 #
706 # ## BUG bash STDOUT:
707 # a[@]: 'no-colon' 'with-colon'
708 # a[*]: 'no-colon' 'with-colon'
709 # a[@]: '' ''
710 # a[*]: '' ''
711 # a[@]: ' ' ' '
712 # a[*]: ' ' ' '
713 # a[@]: ' ' ' '
714 # a[*]: '' ''
715 # ## END
716
717 # Zsh's ${a} and ${a[@]} implement something different from the other shells'.
718
719 ## OK zsh STDOUT:
720 a[@]: '' 'with-colon'
721 a[*]: '' 'with-colon'
722 a[@]: '' ''
723 a[*]: '' 'with-colon'
724 a[@]: ' ' ' '
725 a[*]: ' ' ' '
726 a[@]: ' ' ' '
727 a[*]: '' 'with-colon'
728 ## END
729
730 ## N-I dash STDOUT:
731 ## END:
732
733
734 #### op-test for ${!array} with array="a" and array="a[0]"
735 case $SH in dash|mksh|zsh) exit ;; esac
736
737 test-hyphen() {
738 ref='a'
739 echo "ref=a : '${!ref-no-colon}' '${!ref:-with-colon}'"
740 ref='a[0]'
741 echo "ref=a[0]: '${!ref-no-colon}' '${!ref:-with-colon}'"
742 }
743
744 a=()
745 test-hyphen
746 a=("")
747 test-hyphen
748 a=("" "")
749 test-hyphen
750 IFS=
751 test-hyphen
752
753 ## STDOUT:
754 ref=a : 'no-colon' 'with-colon'
755 ref=a[0]: 'no-colon' 'with-colon'
756 ref=a : '' 'with-colon'
757 ref=a[0]: '' 'with-colon'
758 ref=a : '' 'with-colon'
759 ref=a[0]: '' 'with-colon'
760 ref=a : '' 'with-colon'
761 ref=a[0]: '' 'with-colon'
762 ## END
763
764 ## N-I dash/mksh/zsh STDOUT:
765 ## END:
766
767
768 #### op-test for ${!array} with array="a[@]" or array="a[*]"
769 case $SH in dash|mksh|zsh) exit ;; esac
770
771 test-hyphen() {
772 ref='a[@]'
773 echo "ref=a[@]: '${!ref-no-colon}' '${!ref:-with-colon}'"
774 ref='a[*]'
775 echo "ref=a[*]: '${!ref-no-colon}' '${!ref:-with-colon}'"
776 }
777
778 a=()
779 test-hyphen
780 a=("")
781 test-hyphen
782 a=("" "")
783 test-hyphen
784 IFS=
785 test-hyphen
786
787 ## STDOUT:
788 ref=a[@]: 'no-colon' 'with-colon'
789 ref=a[*]: 'no-colon' 'with-colon'
790 ref=a[@]: '' 'with-colon'
791 ref=a[*]: '' 'with-colon'
792 ref=a[@]: ' ' ' '
793 ref=a[*]: ' ' ' '
794 ref=a[@]: ' ' ' '
795 ref=a[*]: '' 'with-colon'
796 ## END
797
798 ## BUG bash STDOUT:
799 ref=a[@]: 'no-colon' 'with-colon'
800 ref=a[*]: 'no-colon' 'with-colon'
801 ref=a[@]: '' ''
802 ref=a[*]: '' ''
803 ref=a[@]: ' ' ' '
804 ref=a[*]: ' ' ' '
805 ref=a[@]: ' ' ' '
806 ref=a[*]: '' ''
807 ## END
808
809 ## N-I dash/mksh/zsh STDOUT:
810 ## END:
811
812
813 #### op-test for unquoted ${a[*]:-empty} with IFS=
814 case $SH in dash) exit ;; esac
815
816 IFS=
817 a=("" "")
818 argv.py ${a[*]:-empty}
819
820 ## STDOUT:
821 []
822 ## END
823
824 ## BUG mksh STDOUT:
825 ['empty']
826 ## END
827
828 ## N-I dash STDOUT:
829 ## END: