1 ## compare_shells: bash mksh
2 ## oils_failures_allowed: 5
3
4 #### nounset / set -u with empty array (bug in bash 4.3, fixed in 4.4)
5
6 # http://lists.gnu.org/archive/html/help-bash/2017-09/msg00005.html
7
8 set -o nounset
9 empty=()
10 argv.py "${empty[@]}"
11 echo status=$?
12 ## STDOUT:
13 []
14 status=0
15 ## END
16 ## BUG mksh stdout-json: ""
17 ## BUG mksh status: 1
18
19 #### local array
20 # mksh support local variables, but not local arrays, oddly.
21 f() {
22 local a=(1 '2 3')
23 argv.py "${a[0]}"
24 }
25 f
26 ## stdout: ['1']
27 ## status: 0
28 ## BUG mksh status: 1
29 ## BUG mksh stdout-json: ""
30
31 #### Command with with word splitting in array
32 array=('1 2' $(echo '3 4'))
33 argv.py "${array[@]}"
34 ## stdout: ['1 2', '3', '4']
35
36 #### space before ( in array initialization
37 # NOTE: mksh accepts this, but bash doesn't
38 a= (1 '2 3')
39 echo $a
40 ## status: 2
41 ## OK mksh status: 0
42 ## OK mksh stdout: 1
43
44 #### array over multiple lines
45 a=(
46 1
47 '2 3'
48 )
49 argv.py "${a[@]}"
50 ## stdout: ['1', '2 3']
51 ## status: 0
52
53 #### array with invalid token
54 a=(
55 1
56 &
57 '2 3'
58 )
59 argv.py "${a[@]}"
60 ## status: 2
61 ## OK mksh status: 1
62
63 #### array with empty string
64 empty=('')
65 argv.py "${empty[@]}"
66 ## stdout: ['']
67
68 #### Retrieve index
69 a=(1 '2 3')
70 argv.py "${a[1]}"
71 ## stdout: ['2 3']
72
73 #### Retrieve out of bounds index
74 a=(1 '2 3')
75 argv.py "${a[3]}"
76 ## stdout: ['']
77
78 #### Negative index
79 a=(1 '2 3')
80 argv.py "${a[-1]}" "${a[-2]}" "${a[-5]}" # last one out of bounds
81 ## stdout: ['2 3', '1', '']
82 ## N-I mksh stdout: ['', '', '']
83
84 #### Negative index and sparse array
85 a=(0 1 2 3 4)
86 unset a[1]
87 unset a[4]
88 echo "${a[@]}"
89 echo -1 ${a[-1]}
90 echo -2 ${a[-2]}
91 echo -3 ${a[-3]}
92 echo -4 ${a[-4]}
93 echo -5 ${a[-5]}
94
95 a[-1]+=0 # append 0 on the end
96 echo ${a[@]}
97 (( a[-1] += 42 ))
98 echo ${a[@]}
99
100 ## STDOUT:
101 0 2 3
102 -1 3
103 -2 2
104 -3
105 -4 0
106 -5
107 0 2 30
108 0 2 72
109 ## END
110 ## BUG mksh STDOUT:
111 0 2 3
112 -1
113 -2
114 -3
115 -4
116 -5
117 0 2 3 0
118 0 2 3 42
119 ## END
120
121 #### Negative index and sparse array
122 a=(0 1)
123 unset 'a[-1]' # remove last element
124 a+=(2 3)
125 echo ${a[0]} $((a[0]))
126 echo ${a[1]} $((a[1]))
127 echo ${a[2]} $((a[2]))
128 echo ${a[3]} $((a[3]))
129 ## STDOUT:
130 0 0
131 2 2
132 3 3
133 0
134 ## END
135 ## BUG mksh STDOUT:
136 0 0
137 1 1
138 2 2
139 3 3
140 ## END
141
142 #### Length after unset
143 a=(0 1 2 3)
144 unset a[-1]
145 echo len=${#a[@]}
146 unset a[-1]
147 echo len=${#a[@]}
148 ## STDOUT:
149 len=3
150 len=2
151 ## END
152 ## BUG mksh STDOUT:
153 len=4
154 len=4
155 ## END
156
157 #### Retrieve index that is a variable
158 a=(1 '2 3')
159 i=1
160 argv.py "${a[$i]}"
161 ## stdout: ['2 3']
162
163 #### Retrieve index that is a variable without $
164 a=(1 '2 3')
165 i=5
166 argv.py "${a[i-4]}"
167 ## stdout: ['2 3']
168
169 #### Retrieve index that is a command sub
170 a=(1 '2 3')
171 argv.py "${a[$(echo 1)]}"
172 ## stdout: ['2 3']
173
174 #### Retrieve array indices with ${!a}
175 a=(1 '2 3')
176 argv.py "${!a[@]}"
177 ## stdout: ['0', '1']
178
179 #### Retrieve sparse array indices with ${!a}
180 a=()
181 (( a[99]=1 ))
182 argv.py "${!a[@]}"
183 ## STDOUT:
184 ['99']
185 ## END
186
187 #### ${!a[1]} is named ref in bash
188 # mksh ignores it
189 foo=bar
190 a=('1 2' foo '2 3')
191 argv.py "${!a[1]}"
192 ## status: 0
193 ## stdout: ['bar']
194 ## N-I mksh stdout: ['a[1]']
195
196 #### ${!a} on array
197
198 # bash gives empty string because it's like a[0]
199 # mksh gives the name of the variable with !. Very weird.
200
201 a=(1 '2 3')
202 argv.py "${!a}"
203
204 ## stdout: ['']
205 ## status: 0
206 ## BUG mksh stdout: ['a']
207 ## BUG mksh status: 0
208
209 #### All elements unquoted
210 a=(1 '2 3')
211 argv.py ${a[@]}
212 ## stdout: ['1', '2', '3']
213
214 #### All elements quoted
215 a=(1 '2 3')
216 argv.py "${a[@]}"
217 ## stdout: ['1', '2 3']
218
219 #### $*
220 a=(1 '2 3')
221 argv.py ${a[*]}
222 ## stdout: ['1', '2', '3']
223
224 #### "$*"
225 a=(1 '2 3')
226 argv.py "${a[*]}"
227 ## stdout: ['1 2 3']
228
229 #### Interpolate array into array
230 a=(1 '2 3')
231 a=(0 "${a[@]}" '4 5')
232 argv.py "${a[@]}"
233 ## stdout: ['0', '1', '2 3', '4 5']
234
235 #### Exporting array doesn't do anything, not even first element
236 # bash parses, but doesn't execute.
237 # mksh gives syntax error -- parses differently with 'export'
238 # osh no longer parses this statically.
239
240 export PYTHONPATH
241
242 PYTHONPATH=mystr # NOTE: in bash, this doesn't work afterward!
243 printenv.py PYTHONPATH
244
245 PYTHONPATH=(myarray)
246 printenv.py PYTHONPATH
247
248 PYTHONPATH=(a b c)
249 printenv.py PYTHONPATH
250
251 ## status: 0
252 ## STDOUT:
253 mystr
254 None
255 None
256 ## END
257
258 #### strict_array prevents exporting array
259
260 shopt -s strict_array
261
262 export PYTHONPATH
263 PYTHONPATH=(a b c)
264 printenv.py PYTHONPATH
265
266 ## status: 1
267 ## STDOUT:
268 ## END
269
270 ## N-I bash/mksh status: 0
271 ## N-I bash/mksh STDOUT:
272 None
273 ## END
274
275 #### Arrays can't be used as env bindings
276 # Hm bash it treats it as a string!
277 A=a B=(b b) printenv.py A B
278 ## status: 2
279 ## stdout-json: ""
280 ## OK bash stdout-json: "a\n(b b)\n"
281 ## OK bash status: 0
282 ## OK mksh status: 1
283
284 #### Set element
285 a=(1 '2 3')
286 a[0]=9
287 argv.py "${a[@]}"
288 ## stdout: ['9', '2 3']
289
290 #### Set element with var ref
291 a=(1 '2 3')
292 i=0
293 a[$i]=9
294 argv.py "${a[@]}"
295 ## stdout: ['9', '2 3']
296
297 #### Set element with array ref
298 # This makes parsing a little more complex. Anything can be inside [],
299 # including other [].
300 a=(1 '2 3')
301 i=(0 1)
302 a[${i[1]}]=9
303 argv.py "${a[@]}"
304 ## stdout: ['1', '9']
305
306 #### Set array item to array
307 a=(1 2)
308 a[0]=(3 4)
309 echo "status=$?"
310 ## stdout-json: ""
311 ## status: 2
312 ## N-I mksh status: 1
313 ## BUG bash stdout: status=1
314 ## BUG bash status: 0
315
316 #### Slice of array with [@]
317 # mksh doesn't support this syntax! It's a bash extension.
318 a=(1 2 3)
319 argv.py "${a[@]:1:2}"
320 ## stdout: ['2', '3']
321 ## N-I mksh status: 1
322 ## N-I mksh stdout-json: ""
323
324 #### Negative slice begin
325 # mksh doesn't support this syntax! It's a bash extension.
326 # NOTE: for some reason -2) has to be in parens? Ah that's because it
327 # conflicts with :-! That's silly. You can also add a space.
328 a=(1 2 3 4 5)
329 argv.py "${a[@]:(-4)}"
330 ## stdout: ['2', '3', '4', '5']
331 ## N-I mksh status: 1
332 ## N-I mksh stdout-json: ""
333
334 #### Negative slice length
335 a=(1 2 3 4 5)
336 argv.py "${a[@]: 1: -3}"
337 ## status: 1
338 ## stdout-json: ""
339
340 #### Slice with arithmetic
341 a=(1 2 3)
342 i=5
343 argv.py "${a[@]:i-4:2}"
344 ## stdout: ['2', '3']
345 ## N-I mksh status: 1
346 ## N-I mksh stdout-json: ""
347
348 #### Number of elements
349 a=(1 '2 3')
350 echo "${#a[@]}" ${#a[@]} # bug fix: also test without quotes
351 ## stdout: 2 2
352
353 #### Length of an element
354 a=(1 '2 3')
355 echo "${#a[1]}"
356 ## stdout: 3
357
358 #### Iteration
359 a=(1 '2 3')
360 for v in "${a[@]}"; do
361 echo $v
362 done
363 ## stdout-json: "1\n2 3\n"
364
365 #### glob within array yields separate elements
366 touch _tmp/y.Y _tmp/yy.Y
367 a=(_tmp/*.Y)
368 argv.py "${a[@]}"
369 ## stdout: ['_tmp/y.Y', '_tmp/yy.Y']
370
371 #### declare array and then append
372 declare -a array
373 array+=(a)
374 array+=(b c)
375 argv.py "${array[@]}"
376 ## stdout: ['a', 'b', 'c']
377
378 #### Array syntax in wrong place
379 ls foo=(1 2)
380 ## status: 1
381 ## OK bash status: 2
382
383 #### Single array with :-
384
385 # 2024-06 - bash 5.2 and mksh now match, bash 4.4 differed.
386 # Could change OSH
387 # zsh agrees with OSH, but it fails most test cases
388
389 single=('')
390 argv.py ${single[@]:-none} x "${single[@]:-none}"
391 ## stdout: ['none', 'x', 'none']
392
393 #### Stripping a whole array unquoted
394 # Problem: it joins it first.
395 files=('foo.c' 'sp ace.h' 'bar.c')
396 argv.py ${files[@]%.c}
397 ## status: 0
398 ## stdout: ['foo', 'sp', 'ace.h', 'bar']
399 ## N-I mksh status: 1
400 ## N-I mksh stdout-json: ""
401
402 #### Stripping a whole array quoted
403 files=('foo.c' 'sp ace.h' 'bar.c')
404 argv.py "${files[@]%.c}"
405 ## status: 0
406 ## stdout: ['foo', 'sp ace.h', 'bar']
407 ## N-I mksh status: 1
408 ## N-I mksh stdout-json: ""
409
410 #### Multiple subscripts not allowed
411 # NOTE: bash 4.3 had a bug where it ignored the bad subscript, but now it is
412 # fixed.
413 a=('123' '456')
414 argv.py "${a[0]}" "${a[0][0]}"
415 ## stdout-json: ""
416 ## status: 2
417 ## OK bash/mksh status: 1
418
419 #### Length op, index op, then transform op is not allowed
420 a=('123' '456')
421 echo "${#a[0]}" "${#a[0]/1/xxx}"
422 ## stdout-json: ""
423 ## status: 2
424 ## OK bash/mksh status: 1
425
426 #### ${mystr[@]} and ${mystr[*]} are no-ops
427 s='abc'
428 echo ${s[@]}
429 echo ${s[*]}
430 ## STDOUT:
431 abc
432 abc
433 ## END
434
435 #### ${mystr[@]} and ${mystr[*]} disallowed with strict_array
436
437 $SH -c 'shopt -s strict_array; s="abc"; echo ${s[@]}'
438 echo status=$?
439
440 $SH -c 'shopt -s strict_array; s="abc"; echo ${s[*]}'
441 echo status=$?
442
443 ## status: 0
444 ## STDOUT:
445 status=1
446 status=1
447 ## END
448 ## N-I bash/mksh STDOUT:
449 abc
450 status=0
451 abc
452 status=0
453 ## END
454
455 #### Create a "user" array out of the argv array
456 set -- 'a b' 'c'
457 array1=('x y' 'z')
458 array2=("$@")
459 argv.py "${array1[@]}" "${array2[@]}"
460 ## stdout: ['x y', 'z', 'a b', 'c']
461
462 #### Tilde expansion within array
463 HOME=/home/bob
464 a=(~/src ~/git)
465 echo "${a[@]}"
466 ## stdout: /home/bob/src /home/bob/git
467
468 #### Brace Expansion within Array
469 a=(-{a,b} {c,d}-)
470 echo "${a[@]}"
471 ## stdout: -a -b c- d-
472
473 #### array default
474 default=('1 2' '3')
475 argv.py "${undef[@]:-${default[@]}}"
476 ## stdout: ['1 2', '3']
477
478 #### Singleton Array Copy and Assign. OSH can't index strings with ints
479 a=( '12 3' )
480 b=( "${a[@]}" )
481 c="${a[@]}" # This decays it to a string
482 d=${a[*]} # This decays it to a string
483 echo ${#a[0]} ${#b[0]}
484 echo ${#a[@]} ${#b[@]}
485
486 # osh is intentionally stricter, and these fail.
487 echo ${#c[0]} ${#d[0]}
488 echo ${#c[@]} ${#d[@]}
489
490 ## status: 1
491 ## STDOUT:
492 4 4
493 1 1
494 ## END
495 ## OK bash/mksh status: 0
496 ## OK bash/mksh STDOUT:
497 4 4
498 1 1
499 4 4
500 1 1
501 ## END
502
503 #### declare -a / local -a is empty array
504 declare -a myarray
505 argv.py "${myarray[@]}"
506 myarray+=('x')
507 argv.py "${myarray[@]}"
508
509 f() {
510 local -a myarray
511 argv.py "${myarray[@]}"
512 myarray+=('x')
513 argv.py "${myarray[@]}"
514 }
515 f
516 ## STDOUT:
517 []
518 ['x']
519 []
520 ['x']
521 ## END
522
523 #### Create sparse array
524 a=()
525 (( a[99]=1 )) # osh doesn't parse index assignment outside arithmetic yet
526 echo len=${#a[@]}
527 argv.py "${a[@]}"
528 echo "unset=${a[33]}"
529 echo len-of-unset=${#a[33]}
530 ## STDOUT:
531 len=1
532 ['1']
533 unset=
534 len-of-unset=0
535 ## END
536
537 #### Create sparse array implicitly
538 (( a[99]=1 ))
539 echo len=${#a[@]}
540 argv.py "${a[@]}"
541 echo "unset=${a[33]}"
542 echo len-of-unset=${#a[33]}
543 ## STDOUT:
544 len=1
545 ['1']
546 unset=
547 len-of-unset=0
548 ## END
549
550 #### Append sparse arrays
551 a=()
552 (( a[99]=1 ))
553 b=()
554 (( b[33]=2 ))
555 (( b[66]=3 ))
556 a+=( "${b[@]}" )
557 argv.py "${a[@]}"
558 argv.py "${a[99]}" "${a[100]}" "${a[101]}"
559 ## STDOUT:
560 ['1', '2', '3']
561 ['1', '2', '3']
562 ## END
563
564 #### Slice of sparse array with [@]
565 # mksh doesn't support this syntax! It's a bash extension.
566 (( a[33]=1 ))
567 (( a[66]=2 ))
568 (( a[99]=2 ))
569 argv.py "${a[@]:15:2}"
570 ## stdout: ['1', '2']
571 ## N-I mksh status: 1
572 ## N-I mksh stdout-json: ""
573
574 #### Using an array itself as the index on LHS
575 shopt -u strict_arith
576 a[a]=42
577 a[a]=99
578 argv.py "${a[@]}" "${a[0]}" "${a[42]}" "${a[99]}"
579
580 ## status: 0
581 ## STDOUT:
582 ['42', '99', '42', '99', '']
583 ## END
584
585 #### Using an array itself as the index on RHS
586 shopt -u strict_arith
587 a=(1 2 3)
588 (( x = a[a] ))
589 echo $x
590 ## status: 0
591 ## STDOUT:
592 2
593 ## END
594
595 #### a[$x$y] on LHS and RHS
596 x=1
597 y=2
598 a[$x$y]=foo
599
600 # not allowed by OSH parsing
601 #echo ${a[$x$y]}
602
603 echo ${a[12]}
604 echo ${#a[@]}
605
606 ## STDOUT:
607 foo
608 1
609 ## END
610
611
612 #### Dynamic parsing of LHS a[$code]=value
613
614 declare -a array
615 array[x=1]='one'
616
617 code='y=2'
618 #code='1+2' # doesn't work either
619 array[$code]='two'
620
621 argv.py "${array[@]}"
622 echo x=$x
623 echo y=$y
624
625 ## STDOUT:
626 ['one', 'two']
627 x=1
628 y=2
629 ## END
630 ## N-I dash stdout-json: ""
631 ## N-I dash status: 2
632
633 #### Dynamic parsing of RHS ${a[$code]}
634 declare -a array
635 array=(zero one two three)
636
637 echo ${array[1+2]}
638
639 code='1+2'
640 echo ${array[$code]}
641
642 ## STDOUT:
643 three
644 three
645 ## END
646
647 # it still dynamically parses
648
649 ## OK zsh STDOUT:
650 two
651 two
652 ## END
653
654
655 #### test membership with test -v
656
657 # note: modern versions of zsh implement this
658
659 array=(1 2 3)
660 test -v 'array[1]'
661 echo status=$?
662
663 test -v 'array[4]'
664 echo status=$?
665
666 echo
667
668 # dynamic arithmetic
669 array=(1 2 3)
670 test -v 'array[1+1]'
671 echo status=$?
672
673 test -v 'array[4+1]'
674 echo status=$?
675
676 ## STDOUT:
677 status=0
678 status=1
679
680 status=0
681 status=1
682 ## END
683
684 ## N-I mksh STDOUT:
685 status=2
686 status=2
687
688 status=2
689 status=2
690 ## END
691
692 #### test membership with [[ -v
693
694 # note: modern versions of zsh implement this
695
696 array=(1 2 3)
697 [[ -v array[1] ]]
698 echo status=$?
699
700 [[ -v array[4] ]]
701 echo status=$?
702
703 echo
704
705 [[ -v array[1+1] ]]
706 echo status=$?
707
708 [[ -v array[4+1] ]]
709 echo status=$?
710
711 ## STDOUT:
712 status=0
713 status=1
714
715 status=0
716 status=1
717 ## END
718
719 ## N-I mksh status: 1
720 ## N-I mksh STDOUT:
721 ## END
722
723 #### test -v array[i] with empty string
724
725 typeset -a array
726 array=('' nonempty)
727
728 [[ -v array[0] ]]
729 echo zero=$?
730
731 [[ -v array[1] ]]
732 echo one=$?
733
734 [[ -v array[2] ]]
735 echo two=$?
736
737 ## STDOUT:
738 zero=0
739 one=0
740 two=1
741 ## END
742
743 ## N-I mksh status: 1
744 ## N-I mksh STDOUT:
745 ## END
746
747
748 #### [[ -v array[expr]] ]] does arith expression evaluation
749
750 typeset -a array
751 array=('' nonempty)
752
753 # This feels inconsistent with the rest of bash?
754 zero=0
755
756 [[ -v array[zero+0] ]]
757 echo zero=$?
758
759 [[ -v array[zero+1] ]]
760 echo one=$?
761
762 [[ -v array[zero+2] ]]
763 echo two=$?
764
765 echo ---
766
767 i='0+0'
768 [[ -v array[i] ]]
769 echo zero=$?
770
771 i='0+1'
772 [[ -v array[i] ]]
773 echo one=$?
774
775 i='0+2'
776 [[ -v array[i] ]]
777 echo two=$?
778
779 echo ---
780
781 i='0+0'
782 [[ -v array[$i] ]]
783 echo zero=$?
784
785 i='0+1'
786 [[ -v array[$i] ]]
787 echo one=$?
788
789 i='0+2'
790 [[ -v array[$i] ]]
791 echo two=$?
792
793
794 ## STDOUT:
795 zero=0
796 one=0
797 two=1
798 ---
799 zero=0
800 one=0
801 two=1
802 ---
803 zero=0
804 one=0
805 two=1
806 ## END
807
808 ## N-I mksh status: 1
809 ## N-I mksh STDOUT:
810 ## END