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