OILS / spec / array.test.sh View on Github | oilshell.org

681 lines, 314 significant
1## compare_shells: bash mksh
2## oils_failures_allowed: 1
3
4# TODO: Need a SETUP section.
5
6#### SETUP
7a=(1 '2 3')
8
9#### "${a[@]}" and "${a[*]}"
10a=(1 '2 3')
11argv.py "${a[@]}" "${a[*]}"
12## stdout: ['1', '2 3', '1 2 3']
13
14#### ${a[@]} and ${a[*]}
15a=(1 '2 3')
16argv.py ${a[@]} ${a[*]}
17## stdout: ['1', '2', '3', '1', '2', '3']
18
19#### 4 ways to interpolate empty array
20argv.py 1 "${a[@]}" 2 ${a[@]} 3 "${a[*]}" 4 ${a[*]} 5
21## stdout: ['1', '2', '3', '', '4', '5']
22
23#### empty array
24empty=()
25argv.py "${empty[@]}"
26## stdout: []
27
28#### Empty array with :-
29empty=()
30argv.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
37set -o nounset
38empty=()
39argv.py "${empty[@]}"
40echo status=$?
41## STDOUT:
42[]
43status=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.
50f() {
51 local a=(1 '2 3')
52 argv.py "${a[0]}"
53}
54f
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
61array=('1 2' $(echo '3 4'))
62argv.py "${array[@]}"
63## stdout: ['1 2', '3', '4']
64
65#### space before ( in array initialization
66# NOTE: mksh accepts this, but bash doesn't
67a= (1 '2 3')
68echo $a
69## status: 2
70## OK mksh status: 0
71## OK mksh stdout: 1
72
73#### array over multiple lines
74a=(
751
76'2 3'
77)
78argv.py "${a[@]}"
79## stdout: ['1', '2 3']
80## status: 0
81
82#### array with invalid token
83a=(
841
85&
86'2 3'
87)
88argv.py "${a[@]}"
89## status: 2
90## OK mksh status: 1
91
92#### array with empty string
93empty=('')
94argv.py "${empty[@]}"
95## stdout: ['']
96
97#### Retrieve index
98a=(1 '2 3')
99argv.py "${a[1]}"
100## stdout: ['2 3']
101
102#### Retrieve out of bounds index
103a=(1 '2 3')
104argv.py "${a[3]}"
105## stdout: ['']
106
107#### Negative index
108a=(1 '2 3')
109argv.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
114a=(0 1 2 3 4)
115unset a[1]
116unset a[4]
117echo "${a[@]}"
118echo -1 ${a[-1]}
119echo -2 ${a[-2]}
120echo -3 ${a[-3]}
121echo -4 ${a[-4]}
122echo -5 ${a[-5]}
123
124a[-1]+=0 # append 0 on the end
125echo ${a[@]}
126(( a[-1] += 42 ))
127echo ${a[@]}
128
129## STDOUT:
1300 2 3
131-1 3
132-2 2
133-3
134-4 0
135-5
1360 2 30
1370 2 72
138## END
139## BUG mksh STDOUT:
1400 2 3
141-1
142-2
143-3
144-4
145-5
1460 2 3 0
1470 2 3 42
148## END
149
150#### Negative index and sparse array
151a=(0 1)
152unset 'a[-1]' # remove last element
153a+=(2 3)
154echo ${a[0]} $((a[0]))
155echo ${a[1]} $((a[1]))
156echo ${a[2]} $((a[2]))
157echo ${a[3]} $((a[3]))
158## STDOUT:
1590 0
1602 2
1613 3
1620
163## END
164## BUG mksh STDOUT:
1650 0
1661 1
1672 2
1683 3
169## END
170
171#### Length after unset
172a=(0 1 2 3)
173unset a[-1]
174echo len=${#a[@]}
175unset a[-1]
176echo len=${#a[@]}
177## STDOUT:
178len=3
179len=2
180## END
181## BUG mksh STDOUT:
182len=4
183len=4
184## END
185
186#### Retrieve index that is a variable
187a=(1 '2 3')
188i=1
189argv.py "${a[$i]}"
190## stdout: ['2 3']
191
192#### Retrieve index that is a variable without $
193a=(1 '2 3')
194i=5
195argv.py "${a[i-4]}"
196## stdout: ['2 3']
197
198#### Retrieve index that is a command sub
199a=(1 '2 3')
200argv.py "${a[$(echo 1)]}"
201## stdout: ['2 3']
202
203#### Retrieve array indices with ${!a}
204a=(1 '2 3')
205argv.py "${!a[@]}"
206## stdout: ['0', '1']
207
208#### Retrieve sparse array indices with ${!a}
209a=()
210(( a[99]=1 ))
211argv.py "${!a[@]}"
212## STDOUT:
213['99']
214## END
215
216#### ${!a[1]} is named ref in bash
217# mksh ignores it
218foo=bar
219a=('1 2' foo '2 3')
220argv.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
230a=(1 '2 3')
231argv.py "${!a}"
232
233## stdout: ['']
234## status: 0
235## BUG mksh stdout: ['a']
236## BUG mksh status: 0
237
238#### All elements unquoted
239a=(1 '2 3')
240argv.py ${a[@]}
241## stdout: ['1', '2', '3']
242
243#### All elements quoted
244a=(1 '2 3')
245argv.py "${a[@]}"
246## stdout: ['1', '2 3']
247
248#### $*
249a=(1 '2 3')
250argv.py ${a[*]}
251## stdout: ['1', '2', '3']
252
253#### "$*"
254a=(1 '2 3')
255argv.py "${a[*]}"
256## stdout: ['1 2 3']
257
258#### Interpolate array into array
259a=(1 '2 3')
260a=(0 "${a[@]}" '4 5')
261argv.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
269export PYTHONPATH
270
271PYTHONPATH=mystr # NOTE: in bash, this doesn't work afterward!
272printenv.py PYTHONPATH
273
274PYTHONPATH=(myarray)
275printenv.py PYTHONPATH
276
277PYTHONPATH=(a b c)
278printenv.py PYTHONPATH
279
280## status: 0
281## STDOUT:
282mystr
283None
284None
285## END
286
287#### strict_array prevents exporting array
288
289shopt -s strict_array
290
291export PYTHONPATH
292PYTHONPATH=(a b c)
293printenv.py PYTHONPATH
294
295## status: 1
296## STDOUT:
297## END
298
299## N-I bash/mksh status: 0
300## N-I bash/mksh STDOUT:
301None
302## END
303
304#### Arrays can't be used as env bindings
305# Hm bash it treats it as a string!
306A=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
314a=(1 '2 3')
315a[0]=9
316argv.py "${a[@]}"
317## stdout: ['9', '2 3']
318
319#### Set element with var ref
320a=(1 '2 3')
321i=0
322a[$i]=9
323argv.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 [].
329a=(1 '2 3')
330i=(0 1)
331a[${i[1]}]=9
332argv.py "${a[@]}"
333## stdout: ['1', '9']
334
335#### Set array item to array
336a=(1 2)
337a[0]=(3 4)
338echo "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.
347a=(1 2 3)
348argv.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.
357a=(1 2 3 4 5)
358argv.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
364a=(1 2 3 4 5)
365argv.py "${a[@]: 1: -3}"
366## status: 1
367## stdout-json: ""
368
369#### Slice with arithmetic
370a=(1 2 3)
371i=5
372argv.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
378a=(1 '2 3')
379echo "${#a[@]}" ${#a[@]} # bug fix: also test without quotes
380## stdout: 2 2
381
382#### Length of an element
383a=(1 '2 3')
384echo "${#a[1]}"
385## stdout: 3
386
387#### Iteration
388a=(1 '2 3')
389for v in "${a[@]}"; do
390 echo $v
391done
392## stdout-json: "1\n2 3\n"
393
394#### glob within array yields separate elements
395touch _tmp/y.Y _tmp/yy.Y
396a=(_tmp/*.Y)
397argv.py "${a[@]}"
398## stdout: ['_tmp/y.Y', '_tmp/yy.Y']
399
400#### declare array and then append
401declare -a array
402array+=(a)
403array+=(b c)
404argv.py "${array[@]}"
405## stdout: ['a', 'b', 'c']
406
407#### Array syntax in wrong place
408ls 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
418single=('')
419argv.py ${single[@]:-none} x "${single[@]:-none}"
420## stdout: ['none', 'x', 'none']
421
422#### Stripping a whole array unquoted
423# Problem: it joins it first.
424files=('foo.c' 'sp ace.h' 'bar.c')
425argv.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
432files=('foo.c' 'sp ace.h' 'bar.c')
433argv.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.
442a=('123' '456')
443argv.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
449a=('123' '456')
450echo "${#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
456s='abc'
457echo ${s[@]}
458echo ${s[*]}
459## STDOUT:
460abc
461abc
462## END
463
464#### ${mystr[@]} and ${mystr[*]} disallowed with strict_array
465
466$SH -c 'shopt -s strict_array; s="abc"; echo ${s[@]}'
467echo status=$?
468
469$SH -c 'shopt -s strict_array; s="abc"; echo ${s[*]}'
470echo status=$?
471
472## status: 0
473## STDOUT:
474status=1
475status=1
476## END
477## N-I bash/mksh STDOUT:
478abc
479status=0
480abc
481status=0
482## END
483
484#### Create a "user" array out of the argv array
485set -- 'a b' 'c'
486array1=('x y' 'z')
487array2=("$@")
488argv.py "${array1[@]}" "${array2[@]}"
489## stdout: ['x y', 'z', 'a b', 'c']
490
491#### Tilde expansion within array
492HOME=/home/bob
493a=(~/src ~/git)
494echo "${a[@]}"
495## stdout: /home/bob/src /home/bob/git
496
497#### Brace Expansion within Array
498a=(-{a,b} {c,d}-)
499echo "${a[@]}"
500## stdout: -a -b c- d-
501
502#### array default
503default=('1 2' '3')
504argv.py "${undef[@]:-${default[@]}}"
505## stdout: ['1 2', '3']
506
507#### Singleton Array Copy and Assign. OSH can't index strings with ints
508a=( '12 3' )
509b=( "${a[@]}" )
510c="${a[@]}" # This decays it to a string
511d=${a[*]} # This decays it to a string
512echo ${#a[0]} ${#b[0]}
513echo ${#a[@]} ${#b[@]}
514
515# osh is intentionally stricter, and these fail.
516echo ${#c[0]} ${#d[0]}
517echo ${#c[@]} ${#d[@]}
518
519## status: 1
520## STDOUT:
5214 4
5221 1
523## END
524## OK bash/mksh status: 0
525## OK bash/mksh STDOUT:
5264 4
5271 1
5284 4
5291 1
530## END
531
532#### declare -a / local -a is empty array
533declare -a myarray
534argv.py "${myarray[@]}"
535myarray+=('x')
536argv.py "${myarray[@]}"
537
538f() {
539 local -a myarray
540 argv.py "${myarray[@]}"
541 myarray+=('x')
542 argv.py "${myarray[@]}"
543}
544f
545## STDOUT:
546[]
547['x']
548[]
549['x']
550## END
551
552#### Create sparse array
553a=()
554(( a[99]=1 )) # osh doesn't parse index assignment outside arithmetic yet
555echo len=${#a[@]}
556argv.py "${a[@]}"
557echo "unset=${a[33]}"
558echo len-of-unset=${#a[33]}
559## STDOUT:
560len=1
561['1']
562unset=
563len-of-unset=0
564## END
565
566#### Create sparse array implicitly
567(( a[99]=1 ))
568echo len=${#a[@]}
569argv.py "${a[@]}"
570echo "unset=${a[33]}"
571echo len-of-unset=${#a[33]}
572## STDOUT:
573len=1
574['1']
575unset=
576len-of-unset=0
577## END
578
579#### Append sparse arrays
580a=()
581(( a[99]=1 ))
582b=()
583(( b[33]=2 ))
584(( b[66]=3 ))
585a+=( "${b[@]}" )
586argv.py "${a[@]}"
587argv.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 ))
598argv.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
604shopt -u strict_arith
605a[a]=42
606a[a]=99
607argv.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
615shopt -u strict_arith
616a=(1 2 3)
617(( x = a[a] ))
618echo $x
619## status: 0
620## STDOUT:
6212
622## END
623
624#### a[$x$y] on LHS and RHS
625x=1
626y=2
627a[$x$y]=foo
628
629# not allowed by OSH parsing
630#echo ${a[$x$y]}
631
632echo ${a[12]}
633echo ${#a[@]}
634
635## STDOUT:
636foo
6371
638## END
639
640
641#### Dynamic parsing of LHS a[$code]=value
642
643declare -a array
644array[x=1]='one'
645
646code='y=2'
647#code='1+2' # doesn't work either
648array[$code]='two'
649
650argv.py "${array[@]}"
651echo x=$x
652echo y=$y
653
654## STDOUT:
655['one', 'two']
656x=1
657y=2
658## END
659## N-I dash stdout-json: ""
660## N-I dash status: 2
661
662#### Dynamic parsing of RHS ${a[$code]}
663declare -a array
664array=(zero one two three)
665
666echo ${array[1+2]}
667
668code='1+2'
669echo ${array[$code]}
670
671## STDOUT:
672three
673three
674## END
675
676# it still dynamically parses
677
678## OK zsh STDOUT:
679two
680two
681## END