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