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 #### $@ 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 #### assoc array and - and +
403 case $SH in (dash|mksh) exit ;; esac
404
405 declare -A empty=()
406 declare -A assoc=(['k']=v)
407
408 echo empty=${empty[@]-minus}
409 echo empty=${empty[@]+plus}
410 echo assoc=${assoc[@]-minus}
411 echo assoc=${assoc[@]+plus}
412
413 echo ---
414 echo empty=${empty[@]:-minus}
415 echo empty=${empty[@]:+plus}
416 echo assoc=${assoc[@]:-minus}
417 echo assoc=${assoc[@]:+plus}
418 ## STDOUT:
419 empty=minus
420 empty=
421 assoc=v
422 assoc=plus
423 ---
424 empty=minus
425 empty=
426 assoc=v
427 assoc=plus
428 ## END
429
430 ## BUG zsh STDOUT:
431 empty=
432 empty=plus
433 assoc=minus
434 assoc=
435 ---
436 empty=minus
437 empty=
438 assoc=minus
439 assoc=
440 ## END
441
442 ## N-I dash/mksh STDOUT:
443 ## END
444
445
446 #### Error when empty
447 empty=''
448 echo ${empty:?'is em'pty} # test eval of error
449 echo should not get here
450 ## stdout-json: ""
451 ## status: 1
452 ## OK dash status: 2
453
454 #### Error when unset
455 echo ${unset?is empty}
456 echo should not get here
457 ## stdout-json: ""
458 ## status: 1
459 ## OK dash status: 2
460
461 #### Error when unset
462 v=foo
463 echo ${v+v is not unset} ${unset:+is not unset}
464 ## stdout: v is not unset
465
466 #### ${var=x} dynamic scope
467 f() { : "${hello:=x}"; echo $hello; }
468 f
469 echo hello=$hello
470
471 f() { hello=x; }
472 f
473 echo hello=$hello
474 ## STDOUT:
475 x
476 hello=x
477 hello=x
478 ## END
479
480 #### array ${arr[0]=x}
481 arr=()
482 echo ${#arr[@]}
483 : ${arr[0]=x}
484 echo ${#arr[@]}
485 ## STDOUT:
486 0
487 1
488 ## END
489 ## N-I dash status: 2
490 ## N-I dash stdout-json: ""
491 ## N-I zsh status: 1
492 ## N-I zsh stdout-json: "0\n"
493
494 #### assoc array ${arr["k"]=x}
495 # note: this also works in zsh
496
497 declare -A arr=()
498 echo ${#arr[@]}
499 : ${arr['k']=x}
500 echo ${#arr[@]}
501 ## STDOUT:
502 0
503 1
504 ## END
505 ## N-I dash status: 2
506 ## N-I dash stdout-json: ""
507 ## N-I mksh status: 1
508 ## N-I mksh stdout-json: ""
509
510 #### "\z" as arg
511 echo "${undef-\$}"
512 echo "${undef-\(}"
513 echo "${undef-\z}"
514 echo "${undef-\"}"
515 echo "${undef-\`}"
516 echo "${undef-\\}"
517 ## STDOUT:
518 $
519 \(
520 \z
521 "
522 `
523 \
524 ## END
525 ## BUG yash STDOUT:
526 $
527 (
528 z
529 "
530 `
531 \
532 ## END
533 # Note: this line terminates the quoting by ` not to confuse the text editor.
534
535
536 #### "\e" as arg
537 echo "${undef-\e}"
538 ## STDOUT:
539 \e
540 ## END
541 ## BUG zsh/mksh stdout-repr: '\x1b\n'
542 ## BUG yash stdout: e
543
544
545 #### op-test for ${a[@]} and ${a[*]}
546 case $SH in dash) exit ;; esac
547
548 test-hyphen() {
549 echo "a : '${a-no-colon}' '${a:-with-colon}'"
550 echo "a[0]: '${a[0]-no-colon}' '${a[0]:-with-colon}'"
551 echo "a[@]: '${a[@]-no-colon}' '${a[@]:-with-colon}'"
552 echo "a[*]: '${a[*]-no-colon}' '${a[*]:-with-colon}'"
553 }
554
555 a=()
556 test-hyphen
557 a=("")
558 test-hyphen
559 a=("" "")
560 test-hyphen
561 IFS=
562 test-hyphen
563
564 ## STDOUT:
565 a : 'no-colon' 'with-colon'
566 a[0]: 'no-colon' 'with-colon'
567 a[@]: 'no-colon' 'with-colon'
568 a[*]: 'no-colon' 'with-colon'
569 a : '' 'with-colon'
570 a[0]: '' 'with-colon'
571 a[@]: '' 'with-colon'
572 a[*]: '' 'with-colon'
573 a : '' 'with-colon'
574 a[0]: '' 'with-colon'
575 a[@]: ' ' ' '
576 a[*]: ' ' ' '
577 a : '' 'with-colon'
578 a[0]: '' 'with-colon'
579 a[@]: ' ' ' '
580 a[*]: '' 'with-colon'
581 ## END
582
583 # Bash 2.0..4.4 has a bug that "${a[@]:-xxx}" produces an empty string. It
584 # seemed to consider a[@] and a[*] are non-empty when there is at least one
585 # element even if the element is empty. This was fixed in Bash 5.0.
586 #
587 # ## BUG bash STDOUT:
588 # a : 'no-colon' 'with-colon'
589 # a[0]: 'no-colon' 'with-colon'
590 # a[@]: 'no-colon' 'with-colon'
591 # a[*]: 'no-colon' 'with-colon'
592 # a : '' 'with-colon'
593 # a[0]: '' 'with-colon'
594 # a[@]: '' ''
595 # a[*]: '' ''
596 # a : '' 'with-colon'
597 # a[0]: '' 'with-colon'
598 # a[@]: ' ' ' '
599 # a[*]: ' ' ' '
600 # a : '' 'with-colon'
601 # a[0]: '' 'with-colon'
602 # a[@]: ' ' ' '
603 # a[*]: '' ''
604 # ## END
605
606 # Zsh's ${a} and ${a[@]} implement something different from the other shells'.
607
608 ## OK zsh STDOUT:
609 a : '' 'with-colon'
610 a[0]: 'no-colon' 'with-colon'
611 a[@]: '' 'with-colon'
612 a[*]: '' 'with-colon'
613 a : '' 'with-colon'
614 a[0]: 'no-colon' 'with-colon'
615 a[@]: '' ''
616 a[*]: '' 'with-colon'
617 a : ' ' ' '
618 a[0]: 'no-colon' 'with-colon'
619 a[@]: ' ' ' '
620 a[*]: ' ' ' '
621 a : '' 'with-colon'
622 a[0]: 'no-colon' 'with-colon'
623 a[@]: ' ' ' '
624 a[*]: '' 'with-colon'
625 ## END
626
627 ## N-I dash STDOUT:
628 ## END:
629
630
631 #### op-test for ${!array} with array="a[@]" or array="a[*]"
632 case $SH in dash|mksh|zsh) exit ;; esac
633
634 test-hyphen() {
635 ref='a'
636 echo "ref=a : '${!ref-no-colon}' '${!ref:-with-colon}'"
637 ref='a[0]'
638 echo "ref=a[0]: '${!ref-no-colon}' '${!ref:-with-colon}'"
639 ref='a[@]'
640 echo "ref=a[@]: '${!ref-no-colon}' '${!ref:-with-colon}'"
641 ref='a[*]'
642 echo "ref=a[*]: '${!ref-no-colon}' '${!ref:-with-colon}'"
643 }
644
645 a=()
646 test-hyphen
647 a=("")
648 test-hyphen
649 a=("" "")
650 test-hyphen
651 IFS=
652 test-hyphen
653
654 ## STDOUT:
655 ref=a : 'no-colon' 'with-colon'
656 ref=a[0]: 'no-colon' 'with-colon'
657 ref=a[@]: 'no-colon' 'with-colon'
658 ref=a[*]: 'no-colon' 'with-colon'
659 ref=a : '' 'with-colon'
660 ref=a[0]: '' 'with-colon'
661 ref=a[@]: '' 'with-colon'
662 ref=a[*]: '' 'with-colon'
663 ref=a : '' 'with-colon'
664 ref=a[0]: '' 'with-colon'
665 ref=a[@]: ' ' ' '
666 ref=a[*]: ' ' ' '
667 ref=a : '' 'with-colon'
668 ref=a[0]: '' 'with-colon'
669 ref=a[@]: ' ' ' '
670 ref=a[*]: '' 'with-colon'
671 ## END
672
673 ## BUG bash STDOUT:
674 ref=a : 'no-colon' 'with-colon'
675 ref=a[0]: 'no-colon' 'with-colon'
676 ref=a[@]: 'no-colon' 'with-colon'
677 ref=a[*]: 'no-colon' 'with-colon'
678 ref=a : '' 'with-colon'
679 ref=a[0]: '' 'with-colon'
680 ref=a[@]: '' ''
681 ref=a[*]: '' ''
682 ref=a : '' 'with-colon'
683 ref=a[0]: '' 'with-colon'
684 ref=a[@]: ' ' ' '
685 ref=a[*]: ' ' ' '
686 ref=a : '' 'with-colon'
687 ref=a[0]: '' 'with-colon'
688 ref=a[@]: ' ' ' '
689 ref=a[*]: '' ''
690 ## END
691
692 ## N-I dash/mksh/zsh STDOUT:
693 ## END: