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