1 ## compare_shells: bash dash mksh ash yash
2 ## oils_failures_allowed: 7
3
4 # NOTE on bash bug: After setting IFS to array, it never splits anymore? Even
5 # if you assign IFS again.
6
7 #### IFS is scoped
8 IFS=b
9 word=abcd
10 f() { local IFS=c; argv.py $word; }
11 f
12 argv.py $word
13 ## STDOUT:
14 ['ab', 'd']
15 ['a', 'cd']
16 ## END
17
18 #### Tilde sub is not split, but var sub is
19 HOME="foo bar"
20 argv.py ~
21 argv.py $HOME
22 ## STDOUT:
23 ['foo bar']
24 ['foo', 'bar']
25 ## END
26
27 #### Word splitting
28 a="1 2"
29 b="3 4"
30 argv.py $a"$b"
31 ## STDOUT:
32 ['1', '23 4']
33 ## END
34
35 #### Word splitting 2
36 a="1 2"
37 b="3 4"
38 c="5 6"
39 d="7 8"
40 argv.py $a"$b"$c"$d"
41 ## STDOUT:
42 ['1', '23 45', '67 8']
43 ## END
44
45 # Has tests on differences between $* "$*" $@ "$@"
46 # http://stackoverflow.com/questions/448407/bash-script-to-receive-and-repass-quoted-parameters
47
48 #### $*
49 fun() { argv.py -$*-; }
50 fun "a 1" "b 2" "c 3"
51 ## stdout: ['-a', '1', 'b', '2', 'c', '3-']
52
53 #### "$*"
54 fun() { argv.py "-$*-"; }
55 fun "a 1" "b 2" "c 3"
56 ## stdout: ['-a 1 b 2 c 3-']
57
58 #### $@
59 # How does this differ from $* ? I don't think it does.
60 fun() { argv.py -$@-; }
61 fun "a 1" "b 2" "c 3"
62 ## stdout: ['-a', '1', 'b', '2', 'c', '3-']
63
64 #### "$@"
65 fun() { argv.py "-$@-"; }
66 fun "a 1" "b 2" "c 3"
67 ## stdout: ['-a 1', 'b 2', 'c 3-']
68
69 #### empty argv
70 argv.py 1 "$@" 2 $@ 3 "$*" 4 $* 5
71 ## stdout: ['1', '2', '3', '', '4', '5']
72
73 #### $* with empty IFS
74 set -- "1 2" "3 4"
75
76 IFS=
77 argv.py $*
78 argv.py "$*"
79
80 ## STDOUT:
81 ['1 2', '3 4']
82 ['1 23 4']
83 ## END
84
85 #### Word elision with space
86 s1=' '
87 argv.py $s1
88 ## stdout: []
89
90 #### Word elision with non-whitespace IFS
91 # Treated differently than the default IFS. What is the rule here?
92 IFS='_'
93 char='_'
94 space=' '
95 empty=''
96 argv.py $char
97 argv.py $space
98 argv.py $empty
99 ## STDOUT:
100 ['']
101 [' ']
102 []
103 ## END
104 ## BUG yash STDOUT:
105 []
106 [' ']
107 []
108 ## END
109
110 #### Leading/trailing word elision with non-whitespace IFS
111 # This behavior is weird.
112 IFS=_
113 s1='_a_b_'
114 argv.py $s1
115 ## stdout: ['', 'a', 'b']
116
117 #### Leading ' ' vs leading ' _ '
118 # This behavior is weird, but all shells agree.
119 IFS='_ '
120 s1='_ a b _ '
121 s2=' a b _ '
122 argv.py $s1
123 argv.py $s2
124 ## STDOUT:
125 ['', 'a', 'b']
126 ['a', 'b']
127 ## END
128
129 #### Multiple non-whitespace IFS chars.
130 IFS=_-
131 s1='a__b---c_d'
132 argv.py $s1
133 ## stdout: ['a', '', 'b', '', '', 'c', 'd']
134
135 #### IFS with whitespace and non-whitepace.
136 # NOTE: Three delimiters means two empty words in the middle. No elision.
137 IFS='_ '
138 s1='a_b _ _ _ c _d e'
139 argv.py $s1
140 ## stdout: ['a', 'b', '', '', 'c', 'd', 'e']
141
142 #### empty $@ and $* is elided
143 fun() { argv.py 1 $@ $* 2; }
144 fun
145 ## stdout: ['1', '2']
146
147 #### unquoted empty arg is elided
148 empty=""
149 argv.py 1 $empty 2
150 ## stdout: ['1', '2']
151
152 #### unquoted whitespace arg is elided
153 space=" "
154 argv.py 1 $space 2
155 ## stdout: ['1', '2']
156
157 #### empty literals are not elided
158 space=" "
159 argv.py 1 $space"" 2
160 ## stdout: ['1', '', '2']
161
162 #### no splitting when IFS is empty
163 IFS=""
164 foo="a b"
165 argv.py $foo
166 ## stdout: ['a b']
167
168 #### default value can yield multiple words
169 argv.py 1 ${undefined:-"2 3" "4 5"} 6
170 ## stdout: ['1', '2 3', '4 5', '6']
171
172 #### default value can yield multiple words with part joining
173 argv.py 1${undefined:-"2 3" "4 5"}6
174 ## stdout: ['12 3', '4 56']
175
176 #### default value with unquoted IFS char
177 IFS=_
178 argv.py 1${undefined:-"2_3"x_x"4_5"}6
179 ## stdout: ['12_3x', 'x4_56']
180
181 #### IFS empty doesn't do splitting
182 IFS=''
183 x=$(python2 -c 'print(" a b\tc\n")')
184 argv.py $x
185 ## STDOUT:
186 [' a b\tc']
187 ## END
188
189 #### IFS unset behaves like $' \t\n'
190 unset IFS
191 x=$(python2 -c 'print(" a b\tc\n")')
192 argv.py $x
193 ## STDOUT:
194 ['a', 'b', 'c']
195 ## END
196
197 #### IFS='\'
198 # NOTE: OSH fails this because of double backslash escaping issue!
199 IFS='\'
200 s='a\b'
201 argv.py $s
202 ## STDOUT:
203 ['a', 'b']
204 ## END
205
206 #### IFS='\ '
207 # NOTE: OSH fails this because of double backslash escaping issue!
208 # When IFS is \, then you're no longer using backslash escaping.
209 IFS='\ '
210 s='a\b \\ c d\'
211 argv.py $s
212 ## STDOUT:
213 ['a', 'b', '', 'c', 'd']
214 ## END
215
216 #### IFS characters are glob metacharacters
217 IFS='* '
218 s='a*b c'
219 argv.py $s
220
221 IFS='?'
222 s='?x?y?z?'
223 argv.py $s
224
225 IFS='['
226 s='[x[y[z['
227 argv.py $s
228 ## STDOUT:
229 ['a', 'b', 'c']
230 ['', 'x', 'y', 'z']
231 ['', 'x', 'y', 'z']
232 ## END
233
234 #### Trailing space
235 argv.py 'Xec ho '
236 argv.py X'ec ho '
237 argv.py X"ec ho "
238 ## STDOUT:
239 ['Xec ho ']
240 ['Xec ho ']
241 ['Xec ho ']
242 ## END
243
244 #### Empty IFS (regression for bug)
245 IFS=
246 echo ["$*"]
247 set a b c
248 echo ["$*"]
249 ## STDOUT:
250 []
251 [abc]
252 ## END
253
254 #### Unset IFS (regression for bug)
255 set a b c
256 unset IFS
257 echo ["$*"]
258 ## STDOUT:
259 [a b c]
260 ## END
261
262 #### IFS=o (regression for bug)
263 IFS=o
264 echo hi
265 ## STDOUT:
266 hi
267 ## END
268
269 #### IFS and joining arrays
270 IFS=:
271 set -- x 'y z'
272 argv.py "$@"
273 argv.py $@
274 argv.py "$*"
275 argv.py $*
276 ## STDOUT:
277 ['x', 'y z']
278 ['x', 'y z']
279 ['x:y z']
280 ['x', 'y z']
281 ## END
282
283 #### IFS and joining arrays by assignments
284 IFS=:
285 set -- x 'y z'
286
287 s="$@"
288 argv.py "$s"
289
290 s=$@
291 argv.py "$s"
292
293 s="$*"
294 argv.py "$s"
295
296 s=$*
297 argv.py "$s"
298
299 # bash and mksh agree, but this doesn't really make sense to me.
300 # In OSH, "$@" is the only real array, so that's why it behaves differently.
301
302 ## STDOUT:
303 ['x y z']
304 ['x y z']
305 ['x:y z']
306 ['x:y z']
307 ## END
308 ## BUG dash/ash/yash STDOUT:
309 ['x:y z']
310 ['x:y z']
311 ['x:y z']
312 ['x:y z']
313 ## END
314
315
316 # TODO:
317 # - unquoted args of whitespace are not elided (when IFS = null)
318 # - empty quoted args are kept
319 #
320 # - $* $@ with empty IFS
321 # - $* $@ with custom IFS
322 #
323 # - no splitting when IFS is empty
324 # - word splitting removes leading and trailing whitespace
325
326 # TODO: test framework needs common setup
327
328 # Test IFS and $@ $* on all these
329 #### TODO
330 empty=""
331 space=" "
332 AB="A B"
333 X="X"
334 Yspaces=" Y "
335
336
337 #### IFS='' with $@ and $* (bug #627)
338 set -- a 'b c'
339 IFS=''
340 argv.py at $@
341 argv.py star $*
342
343 # zsh agrees
344 ## STDOUT:
345 ['at', 'a', 'b c']
346 ['star', 'a', 'b c']
347 ## END
348
349 #### IFS='' with $@ and $* and printf (bug #627)
350 set -- a 'b c'
351 IFS=''
352 printf '[%s]\n' $@
353 printf '[%s]\n' $*
354 ## STDOUT:
355 [a]
356 [b c]
357 [a]
358 [b c]
359 ## END
360
361 #### IFS='' with ${a[@]} and ${a[*]} (bug #627)
362 case $SH in dash | ash) exit 0 ;; esac
363
364 myarray=(a 'b c')
365 IFS=''
366 argv.py at ${myarray[@]}
367 argv.py star ${myarray[*]}
368
369 ## STDOUT:
370 ['at', 'a', 'b c']
371 ['star', 'a', 'b c']
372 ## END
373 ## N-I dash/ash stdout-json: ""
374
375 #### IFS='' with ${!prefix@} and ${!prefix*} (bug #627)
376 case $SH in dash | mksh | ash | yash) exit 0 ;; esac
377
378 gLwbmGzS_var1=1
379 gLwbmGzS_var2=2
380 IFS=''
381 argv.py at ${!gLwbmGzS_@}
382 argv.py star ${!gLwbmGzS_*}
383
384 ## STDOUT:
385 ['at', 'gLwbmGzS_var1', 'gLwbmGzS_var2']
386 ['star', 'gLwbmGzS_var1', 'gLwbmGzS_var2']
387 ## END
388 ## BUG bash STDOUT:
389 ['at', 'gLwbmGzS_var1', 'gLwbmGzS_var2']
390 ['star', 'gLwbmGzS_var1gLwbmGzS_var2']
391 ## END
392 ## N-I dash/mksh/ash/yash stdout-json: ""
393
394 #### IFS='' with ${!a[@]} and ${!a[*]} (bug #627)
395 case $SH in dash | mksh | ash | yash) exit 0 ;; esac
396
397 IFS=''
398 a=(v1 v2 v3)
399 argv.py at ${!a[@]}
400 argv.py star ${!a[*]}
401
402 ## STDOUT:
403 ['at', '0', '1', '2']
404 ['star', '0', '1', '2']
405 ## END
406 ## BUG bash STDOUT:
407 ['at', '0', '1', '2']
408 ['star', '0 1 2']
409 ## END
410 ## N-I dash/mksh/ash/yash stdout-json: ""
411
412 #### Bug #628 split on : with : in literal word
413
414 # 2025-03: What's the cause of this bug?
415 #
416 # OSH is very wrong here
417 # ['a', '\\', 'b']
418 # Is this a fundamental problem with the IFS state machine?
419 # It definitely relates to the use of backslashes.
420 # So we have at least 4 backslash bugs
421
422 IFS=':'
423 word='a:'
424 argv.py ${word}:b
425 argv.py ${word}:
426
427 echo ---
428
429 # Same thing happens for 'z'
430 IFS='z'
431 word='az'
432 argv.py ${word}zb
433 argv.py ${word}z
434 ## STDOUT:
435 ['a', ':b']
436 ['a', ':']
437 ---
438 ['a', 'zb']
439 ['a', 'z']
440 ## END
441
442 #### Bug #698, similar crash
443 var='\'
444 set -f
445 echo $var
446 ## STDOUT:
447 \
448 ## END
449
450 #### Bug #1664, \\ with noglob
451
452 # Note that we're not changing IFS
453
454 argv.py [\\]_
455 argv.py "[\\]_"
456
457 # TODO: no difference observed here, go back to original bug
458
459 #argv.py [\\_
460 #argv.py "[\\_"
461
462 echo noglob
463
464 # repeat cases with -f, noglob
465 set -f
466
467 argv.py [\\]_
468 argv.py "[\\]_"
469
470 #argv.py [\\_
471 #argv.py "[\\_"
472
473 ## STDOUT:
474 ['[\\]_']
475 ['[\\]_']
476 noglob
477 ['[\\]_']
478 ['[\\]_']
479 ## END
480
481
482 #### Empty IFS bug #2141 (from pnut)
483
484 res=0
485 sum() {
486 # implement callee-save calling convention using `set`
487 # here, we save the value of $res after the function parameters
488 set $@ $res # $1 $2 $3 are now set
489 res=$(($1 + $2))
490 echo "$1 + $2 = $res"
491 res=$3 # restore the value of $res
492 }
493
494 unset IFS
495 sum 12 30 # outputs "12 + 30 = 42"
496
497 IFS=' '
498 sum 12 30 # outputs "12 + 30 = 42"
499
500 IFS=
501 sum 12 30 # outputs "1230 + 0 = 1230"
502
503 # I added this
504 IFS=''
505 sum 12 30
506
507 set -u
508 IFS=
509 sum 12 30 # fails with "fatal: Undefined variable '2'" on res=$(($1 + $2))
510
511 ## STDOUT:
512 12 + 30 = 42
513 12 + 30 = 42
514 12 + 30 = 42
515 12 + 30 = 42
516 12 + 30 = 42
517 ## END
518
519 #### Unicode in IFS
520
521 # bash, zsh, and yash support unicode in IFS, but dash/mksh/ash don't.
522
523 # for zsh, though we're not testing it here
524 setopt SH_WORD_SPLIT
525
526 x=çx IFS=ç
527 printf "<%s>\n" $x
528
529 ## STDOUT:
530 <>
531 <x>
532 ## END
533
534 ## BUG dash/mksh/ash STDOUT:
535 <>
536 <>
537 <x>
538 ## END
539
540 #### 4 x 3 table: (default IFS, IFS='', IFS=zx) x ( $* "$*" $@ "$@" )
541
542 setopt SH_WORD_SPLIT # for zsh
543
544 set -- 'a b' c ''
545
546 # default IFS
547 argv.py ' $* ' $*
548 argv.py ' "$*" ' "$*"
549 argv.py ' $@ ' $@
550 argv.py ' "$@" ' "$@"
551 echo
552
553 IFS=''
554 argv.py ' $* ' $*
555 argv.py ' "$*" ' "$*"
556 argv.py ' $@ ' $@
557 argv.py ' "$@" ' "$@"
558 echo
559
560 IFS=zx
561 argv.py ' $* ' $*
562 argv.py ' "$*" ' "$*"
563 argv.py ' $@ ' $@
564 argv.py ' "$@" ' "$@"
565
566 ## STDOUT:
567 [' $* ', 'a', 'b', 'c']
568 [' "$*" ', 'a b c ']
569 [' $@ ', 'a', 'b', 'c']
570 [' "$@" ', 'a b', 'c', '']
571
572 [' $* ', 'a b', 'c']
573 [' "$*" ', 'a bc']
574 [' $@ ', 'a b', 'c']
575 [' "$@" ', 'a b', 'c', '']
576
577 [' $* ', 'a b', 'c']
578 [' "$*" ', 'a bzcz']
579 [' $@ ', 'a b', 'c']
580 [' "$@" ', 'a b', 'c', '']
581 ## END
582
583 # zsh disagrees on
584 # - $@ with default IFS an
585 # - $@ with IFS=zx
586
587 ## BUG zsh STDOUT:
588 [' $* ', 'a', 'b', 'c']
589 [' "$*" ', 'a b c ']
590 [' $@ ', 'a b', 'c']
591 [' "$@" ', 'a b', 'c', '']
592
593 [' $* ', 'a b', 'c']
594 [' "$*" ', 'a bc']
595 [' $@ ', 'a b', 'c']
596 [' "$@" ', 'a b', 'c', '']
597
598 [' $* ', 'a b', 'c', '']
599 [' "$*" ', 'a bzcz']
600 [' $@ ', 'a b', 'c']
601 [' "$@" ', 'a b', 'c', '']
602 ## END
603
604 ## BUG yash STDOUT:
605 [' $* ', 'a', 'b', 'c', '']
606 [' "$*" ', 'a b c ']
607 [' $@ ', 'a', 'b', 'c', '']
608 [' "$@" ', 'a b', 'c', '']
609
610 [' $* ', 'a b', 'c', '']
611 [' "$*" ', 'a bc']
612 [' $@ ', 'a b', 'c', '']
613 [' "$@" ', 'a b', 'c', '']
614
615 [' $* ', 'a b', 'c', '']
616 [' "$*" ', 'a bzcz']
617 [' $@ ', 'a b', 'c', '']
618 [' "$@" ', 'a b', 'c', '']
619 ## END
620
621 #### 4 x 3 table - with for loop
622 case $SH in yash) exit ;; esac # no echo -n
623
624 setopt SH_WORD_SPLIT # for zsh
625
626 set -- 'a b' c ''
627
628 # default IFS
629 echo -n ' $* '; for i in $*; do echo -n ' '; echo -n -$i-; done; echo
630 echo -n ' "$*" '; for i in "$*"; do echo -n ' '; echo -n -$i-; done; echo
631 echo -n ' $@ '; for i in $@; do echo -n ' '; echo -n -$i-; done; echo
632 echo -n ' "$@" '; for i in "$@"; do echo -n ' '; echo -n -$i-; done; echo
633 echo
634
635 IFS=''
636 echo -n ' $* '; for i in $*; do echo -n ' '; echo -n -$i-; done; echo
637 echo -n ' "$*" '; for i in "$*"; do echo -n ' '; echo -n -$i-; done; echo
638 echo -n ' $@ '; for i in $@; do echo -n ' '; echo -n -$i-; done; echo
639 echo -n ' "$@" '; for i in "$@"; do echo -n ' '; echo -n -$i-; done; echo
640 echo
641
642 IFS=zx
643 echo -n ' $* '; for i in $*; do echo -n ' '; echo -n -$i-; done; echo
644 echo -n ' "$*" '; for i in "$*"; do echo -n ' '; echo -n -$i-; done; echo
645 echo -n ' $@ '; for i in $@; do echo -n ' '; echo -n -$i-; done; echo
646 echo -n ' "$@" '; for i in "$@"; do echo -n ' '; echo -n -$i-; done; echo
647
648 ## STDOUT:
649 $* -a- -b- -c-
650 "$*" -a b c -
651 $@ -a- -b- -c-
652 "$@" -a b- -c- --
653
654 $* -a b- -c-
655 "$*" -a bc-
656 $@ -a b- -c-
657 "$@" -a b- -c- --
658
659 $* -a b- -c-
660 "$*" -a b c -
661 $@ -a b- -c-
662 "$@" -a b- -c- --
663 ## END
664
665 ## N-I yash STDOUT:
666 ## END
667
668 #### IFS=x and '' and $@ - same bug as spec/toysh-posix case #12
669 case $SH in yash) exit ;; esac # no echo -n
670
671 setopt SH_WORD_SPLIT # for zsh
672
673 set -- one '' two
674
675 IFS=zx
676 echo -n ' $* '; for i in $*; do echo -n ' '; echo -n -$i-; done; echo
677 echo -n ' "$*" '; for i in "$*"; do echo -n ' '; echo -n -$i-; done; echo
678 echo -n ' $@ '; for i in $@; do echo -n ' '; echo -n -$i-; done; echo
679 echo -n ' "$@" '; for i in "$@"; do echo -n ' '; echo -n -$i-; done; echo
680
681 argv.py ' $* ' $*
682 argv.py ' "$*" ' "$*"
683 argv.py ' $@ ' $@
684 argv.py ' "$@" ' "$@"
685
686
687 ## OK bash/mksh STDOUT:
688 $* -one- -- -two-
689 "$*" -one two-
690 $@ -one- -- -two-
691 "$@" -one- -- -two-
692 [' $* ', 'one', '', 'two']
693 [' "$*" ', 'onezztwo']
694 [' $@ ', 'one', '', 'two']
695 [' "$@" ', 'one', '', 'two']
696 ## END
697
698 ## STDOUT:
699 $* -one- -two-
700 "$*" -one two-
701 $@ -one- -two-
702 "$@" -one- -- -two-
703 [' $* ', 'one', 'two']
704 [' "$*" ', 'onezztwo']
705 [' $@ ', 'one', 'two']
706 [' "$@" ', 'one', '', 'two']
707 ## END
708
709 ## N-I yash STDOUT:
710 ## END
711
712 #### IFS=x and '' and $@ (#2)
713 setopt SH_WORD_SPLIT # for zsh
714
715 set -- "" "" "" "" ""
716 argv.py =$@=
717 argv.py =$*=
718 echo
719
720 IFS=
721 argv.py =$@=
722 argv.py =$*=
723 echo
724
725 IFS=x
726 argv.py =$@=
727 argv.py =$*=
728
729 ## STDOUT:
730 ['=', '=']
731 ['=', '=']
732
733 ['=', '=']
734 ['=', '=']
735
736 ['=', '=']
737 ['=', '=']
738 ## END
739
740 ## OK bash/mksh STDOUT:
741 ['=', '=']
742 ['=', '=']
743
744 ['=', '=']
745 ['=', '=']
746
747 ['=', '', '', '', '=']
748 ['=', '', '', '', '=']
749 ## END
750
751 # yash-2.49 seems to behave in a strange way, but this behavior seems to have
752 # been fixed at least in yash-2.57.
753
754 ## BUG yash STDOUT:
755 ['=', '', '', '', '=']
756 ['=', '', '', '', '=']
757
758 ['=', '', '', '', '=']
759 ['=', '', '', '', '=']
760
761 ['=', '', '', '', '=']
762 ['=', '', '', '', '=']
763 ## END
764
765 #### IFS=x and '' and $@ (#3)
766 setopt SH_WORD_SPLIT # for zsh
767
768 IFS=x
769 set -- "" "" "" "" ""
770
771 argv.py $*
772 set -- $*
773 argv.py $*
774 set -- $*
775 argv.py $*
776 set -- $*
777 argv.py $*
778 set -- $*
779 argv.py $*
780
781 ## STDOUT:
782 []
783 []
784 []
785 []
786 []
787 ## END
788
789 ## OK bash STDOUT:
790 ['', '', '', '']
791 ['', '', '']
792 ['', '']
793 ['']
794 []
795 ## END
796
797 ## OK mksh STDOUT:
798 ['', '', '']
799 ['']
800 []
801 []
802 []
803 ## END
804
805 ## BUG zsh/yash STDOUT:
806 ['', '', '', '', '']
807 ['', '', '', '', '']
808 ['', '', '', '', '']
809 ['', '', '', '', '']
810 ['', '', '', '', '']
811 ## END
812
813 #### ""$A"" - empty string on both sides - derived from spec/toysh-posix #15
814
815 A=" abc def "
816
817 argv.py $A
818 argv.py ""$A""
819
820 unset IFS
821
822 argv.py $A
823 argv.py ""$A""
824
825 echo
826
827 # Do the same thing in a for loop - this is IDENTICAL behavior
828
829 for i in $A; do echo =$i=; done
830 echo
831
832 for i in ""$A""; do echo =$i=; done
833 echo
834
835 unset IFS
836
837 for i in $A; do echo =$i=; done
838 echo
839
840 for i in ""$A""; do echo =$i=; done
841
842 ## STDOUT:
843 ['abc', 'def']
844 ['', 'abc', 'def', '']
845 ['abc', 'def']
846 ['', 'abc', 'def', '']
847
848 =abc=
849 =def=
850
851 ==
852 =abc=
853 =def=
854 ==
855
856 =abc=
857 =def=
858
859 ==
860 =abc=
861 =def=
862 ==
863 ## END