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

810 lines, 388 significant
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
8set -o nounset
9empty=()
10argv.py "${empty[@]}"
11echo status=$?
12## STDOUT:
13[]
14status=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.
21f() {
22 local a=(1 '2 3')
23 argv.py "${a[0]}"
24}
25f
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
32array=('1 2' $(echo '3 4'))
33argv.py "${array[@]}"
34## stdout: ['1 2', '3', '4']
35
36#### space before ( in array initialization
37# NOTE: mksh accepts this, but bash doesn't
38a= (1 '2 3')
39echo $a
40## status: 2
41## OK mksh status: 0
42## OK mksh stdout: 1
43
44#### array over multiple lines
45a=(
461
47'2 3'
48)
49argv.py "${a[@]}"
50## stdout: ['1', '2 3']
51## status: 0
52
53#### array with invalid token
54a=(
551
56&
57'2 3'
58)
59argv.py "${a[@]}"
60## status: 2
61## OK mksh status: 1
62
63#### array with empty string
64empty=('')
65argv.py "${empty[@]}"
66## stdout: ['']
67
68#### Retrieve index
69a=(1 '2 3')
70argv.py "${a[1]}"
71## stdout: ['2 3']
72
73#### Retrieve out of bounds index
74a=(1 '2 3')
75argv.py "${a[3]}"
76## stdout: ['']
77
78#### Negative index
79a=(1 '2 3')
80argv.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
85a=(0 1 2 3 4)
86unset a[1]
87unset a[4]
88echo "${a[@]}"
89echo -1 ${a[-1]}
90echo -2 ${a[-2]}
91echo -3 ${a[-3]}
92echo -4 ${a[-4]}
93echo -5 ${a[-5]}
94
95a[-1]+=0 # append 0 on the end
96echo ${a[@]}
97(( a[-1] += 42 ))
98echo ${a[@]}
99
100## STDOUT:
1010 2 3
102-1 3
103-2 2
104-3
105-4 0
106-5
1070 2 30
1080 2 72
109## END
110## BUG mksh STDOUT:
1110 2 3
112-1
113-2
114-3
115-4
116-5
1170 2 3 0
1180 2 3 42
119## END
120
121#### Negative index and sparse array
122a=(0 1)
123unset 'a[-1]' # remove last element
124a+=(2 3)
125echo ${a[0]} $((a[0]))
126echo ${a[1]} $((a[1]))
127echo ${a[2]} $((a[2]))
128echo ${a[3]} $((a[3]))
129## STDOUT:
1300 0
1312 2
1323 3
1330
134## END
135## BUG mksh STDOUT:
1360 0
1371 1
1382 2
1393 3
140## END
141
142#### Length after unset
143a=(0 1 2 3)
144unset a[-1]
145echo len=${#a[@]}
146unset a[-1]
147echo len=${#a[@]}
148## STDOUT:
149len=3
150len=2
151## END
152## BUG mksh STDOUT:
153len=4
154len=4
155## END
156
157#### Retrieve index that is a variable
158a=(1 '2 3')
159i=1
160argv.py "${a[$i]}"
161## stdout: ['2 3']
162
163#### Retrieve index that is a variable without $
164a=(1 '2 3')
165i=5
166argv.py "${a[i-4]}"
167## stdout: ['2 3']
168
169#### Retrieve index that is a command sub
170a=(1 '2 3')
171argv.py "${a[$(echo 1)]}"
172## stdout: ['2 3']
173
174#### Retrieve array indices with ${!a}
175a=(1 '2 3')
176argv.py "${!a[@]}"
177## stdout: ['0', '1']
178
179#### Retrieve sparse array indices with ${!a}
180a=()
181(( a[99]=1 ))
182argv.py "${!a[@]}"
183## STDOUT:
184['99']
185## END
186
187#### ${!a[1]} is named ref in bash
188# mksh ignores it
189foo=bar
190a=('1 2' foo '2 3')
191argv.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
201a=(1 '2 3')
202argv.py "${!a}"
203
204## stdout: ['']
205## status: 0
206## BUG mksh stdout: ['a']
207## BUG mksh status: 0
208
209#### All elements unquoted
210a=(1 '2 3')
211argv.py ${a[@]}
212## stdout: ['1', '2', '3']
213
214#### All elements quoted
215a=(1 '2 3')
216argv.py "${a[@]}"
217## stdout: ['1', '2 3']
218
219#### $*
220a=(1 '2 3')
221argv.py ${a[*]}
222## stdout: ['1', '2', '3']
223
224#### "$*"
225a=(1 '2 3')
226argv.py "${a[*]}"
227## stdout: ['1 2 3']
228
229#### Interpolate array into array
230a=(1 '2 3')
231a=(0 "${a[@]}" '4 5')
232argv.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
240export PYTHONPATH
241
242PYTHONPATH=mystr # NOTE: in bash, this doesn't work afterward!
243printenv.py PYTHONPATH
244
245PYTHONPATH=(myarray)
246printenv.py PYTHONPATH
247
248PYTHONPATH=(a b c)
249printenv.py PYTHONPATH
250
251## status: 0
252## STDOUT:
253mystr
254None
255None
256## END
257
258#### strict_array prevents exporting array
259
260shopt -s strict_array
261
262export PYTHONPATH
263PYTHONPATH=(a b c)
264printenv.py PYTHONPATH
265
266## status: 1
267## STDOUT:
268## END
269
270## N-I bash/mksh status: 0
271## N-I bash/mksh STDOUT:
272None
273## END
274
275#### Arrays can't be used as env bindings
276# Hm bash it treats it as a string!
277A=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
285a=(1 '2 3')
286a[0]=9
287argv.py "${a[@]}"
288## stdout: ['9', '2 3']
289
290#### Set element with var ref
291a=(1 '2 3')
292i=0
293a[$i]=9
294argv.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 [].
300a=(1 '2 3')
301i=(0 1)
302a[${i[1]}]=9
303argv.py "${a[@]}"
304## stdout: ['1', '9']
305
306#### Set array item to array
307a=(1 2)
308a[0]=(3 4)
309echo "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.
318a=(1 2 3)
319argv.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.
328a=(1 2 3 4 5)
329argv.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
335a=(1 2 3 4 5)
336argv.py "${a[@]: 1: -3}"
337## status: 1
338## stdout-json: ""
339
340#### Slice with arithmetic
341a=(1 2 3)
342i=5
343argv.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
349a=(1 '2 3')
350echo "${#a[@]}" ${#a[@]} # bug fix: also test without quotes
351## stdout: 2 2
352
353#### Length of an element
354a=(1 '2 3')
355echo "${#a[1]}"
356## stdout: 3
357
358#### Iteration
359a=(1 '2 3')
360for v in "${a[@]}"; do
361 echo $v
362done
363## stdout-json: "1\n2 3\n"
364
365#### glob within array yields separate elements
366touch _tmp/y.Y _tmp/yy.Y
367a=(_tmp/*.Y)
368argv.py "${a[@]}"
369## stdout: ['_tmp/y.Y', '_tmp/yy.Y']
370
371#### declare array and then append
372declare -a array
373array+=(a)
374array+=(b c)
375argv.py "${array[@]}"
376## stdout: ['a', 'b', 'c']
377
378#### Array syntax in wrong place
379ls 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
389single=('')
390argv.py ${single[@]:-none} x "${single[@]:-none}"
391## stdout: ['none', 'x', 'none']
392
393#### Stripping a whole array unquoted
394# Problem: it joins it first.
395files=('foo.c' 'sp ace.h' 'bar.c')
396argv.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
403files=('foo.c' 'sp ace.h' 'bar.c')
404argv.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.
413a=('123' '456')
414argv.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
420a=('123' '456')
421echo "${#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
427s='abc'
428echo ${s[@]}
429echo ${s[*]}
430## STDOUT:
431abc
432abc
433## END
434
435#### ${mystr[@]} and ${mystr[*]} disallowed with strict_array
436
437$SH -c 'shopt -s strict_array; s="abc"; echo ${s[@]}'
438echo status=$?
439
440$SH -c 'shopt -s strict_array; s="abc"; echo ${s[*]}'
441echo status=$?
442
443## status: 0
444## STDOUT:
445status=1
446status=1
447## END
448## N-I bash/mksh STDOUT:
449abc
450status=0
451abc
452status=0
453## END
454
455#### Create a "user" array out of the argv array
456set -- 'a b' 'c'
457array1=('x y' 'z')
458array2=("$@")
459argv.py "${array1[@]}" "${array2[@]}"
460## stdout: ['x y', 'z', 'a b', 'c']
461
462#### Tilde expansion within array
463HOME=/home/bob
464a=(~/src ~/git)
465echo "${a[@]}"
466## stdout: /home/bob/src /home/bob/git
467
468#### Brace Expansion within Array
469a=(-{a,b} {c,d}-)
470echo "${a[@]}"
471## stdout: -a -b c- d-
472
473#### array default
474default=('1 2' '3')
475argv.py "${undef[@]:-${default[@]}}"
476## stdout: ['1 2', '3']
477
478#### Singleton Array Copy and Assign. OSH can't index strings with ints
479a=( '12 3' )
480b=( "${a[@]}" )
481c="${a[@]}" # This decays it to a string
482d=${a[*]} # This decays it to a string
483echo ${#a[0]} ${#b[0]}
484echo ${#a[@]} ${#b[@]}
485
486# osh is intentionally stricter, and these fail.
487echo ${#c[0]} ${#d[0]}
488echo ${#c[@]} ${#d[@]}
489
490## status: 1
491## STDOUT:
4924 4
4931 1
494## END
495## OK bash/mksh status: 0
496## OK bash/mksh STDOUT:
4974 4
4981 1
4994 4
5001 1
501## END
502
503#### declare -a / local -a is empty array
504declare -a myarray
505argv.py "${myarray[@]}"
506myarray+=('x')
507argv.py "${myarray[@]}"
508
509f() {
510 local -a myarray
511 argv.py "${myarray[@]}"
512 myarray+=('x')
513 argv.py "${myarray[@]}"
514}
515f
516## STDOUT:
517[]
518['x']
519[]
520['x']
521## END
522
523#### Create sparse array
524a=()
525(( a[99]=1 )) # osh doesn't parse index assignment outside arithmetic yet
526echo len=${#a[@]}
527argv.py "${a[@]}"
528echo "unset=${a[33]}"
529echo len-of-unset=${#a[33]}
530## STDOUT:
531len=1
532['1']
533unset=
534len-of-unset=0
535## END
536
537#### Create sparse array implicitly
538(( a[99]=1 ))
539echo len=${#a[@]}
540argv.py "${a[@]}"
541echo "unset=${a[33]}"
542echo len-of-unset=${#a[33]}
543## STDOUT:
544len=1
545['1']
546unset=
547len-of-unset=0
548## END
549
550#### Append sparse arrays
551a=()
552(( a[99]=1 ))
553b=()
554(( b[33]=2 ))
555(( b[66]=3 ))
556a+=( "${b[@]}" )
557argv.py "${a[@]}"
558argv.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 ))
569argv.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
575shopt -u strict_arith
576a[a]=42
577a[a]=99
578argv.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
586shopt -u strict_arith
587a=(1 2 3)
588(( x = a[a] ))
589echo $x
590## status: 0
591## STDOUT:
5922
593## END
594
595#### a[$x$y] on LHS and RHS
596x=1
597y=2
598a[$x$y]=foo
599
600# not allowed by OSH parsing
601#echo ${a[$x$y]}
602
603echo ${a[12]}
604echo ${#a[@]}
605
606## STDOUT:
607foo
6081
609## END
610
611
612#### Dynamic parsing of LHS a[$code]=value
613
614declare -a array
615array[x=1]='one'
616
617code='y=2'
618#code='1+2' # doesn't work either
619array[$code]='two'
620
621argv.py "${array[@]}"
622echo x=$x
623echo y=$y
624
625## STDOUT:
626['one', 'two']
627x=1
628y=2
629## END
630## N-I dash stdout-json: ""
631## N-I dash status: 2
632
633#### Dynamic parsing of RHS ${a[$code]}
634declare -a array
635array=(zero one two three)
636
637echo ${array[1+2]}
638
639code='1+2'
640echo ${array[$code]}
641
642## STDOUT:
643three
644three
645## END
646
647# it still dynamically parses
648
649## OK zsh STDOUT:
650two
651two
652## END
653
654
655#### test membership with test -v
656
657# note: modern versions of zsh implement this
658
659array=(1 2 3)
660test -v 'array[1]'
661echo status=$?
662
663test -v 'array[4]'
664echo status=$?
665
666echo
667
668# dynamic arithmetic
669array=(1 2 3)
670test -v 'array[1+1]'
671echo status=$?
672
673test -v 'array[4+1]'
674echo status=$?
675
676## STDOUT:
677status=0
678status=1
679
680status=0
681status=1
682## END
683
684## N-I mksh STDOUT:
685status=2
686status=2
687
688status=2
689status=2
690## END
691
692#### test membership with [[ -v
693
694# note: modern versions of zsh implement this
695
696array=(1 2 3)
697[[ -v array[1] ]]
698echo status=$?
699
700[[ -v array[4] ]]
701echo status=$?
702
703echo
704
705[[ -v array[1+1] ]]
706echo status=$?
707
708[[ -v array[4+1] ]]
709echo status=$?
710
711## STDOUT:
712status=0
713status=1
714
715status=0
716status=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
725typeset -a array
726array=('' nonempty)
727
728[[ -v array[0] ]]
729echo zero=$?
730
731[[ -v array[1] ]]
732echo one=$?
733
734[[ -v array[2] ]]
735echo two=$?
736
737## STDOUT:
738zero=0
739one=0
740two=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
750typeset -a array
751array=('' nonempty)
752
753# This feels inconsistent with the rest of bash?
754zero=0
755
756[[ -v array[zero+0] ]]
757echo zero=$?
758
759[[ -v array[zero+1] ]]
760echo one=$?
761
762[[ -v array[zero+2] ]]
763echo two=$?
764
765echo ---
766
767i='0+0'
768[[ -v array[i] ]]
769echo zero=$?
770
771i='0+1'
772[[ -v array[i] ]]
773echo one=$?
774
775i='0+2'
776[[ -v array[i] ]]
777echo two=$?
778
779echo ---
780
781i='0+0'
782[[ -v array[$i] ]]
783echo zero=$?
784
785i='0+1'
786[[ -v array[$i] ]]
787echo one=$?
788
789i='0+2'
790[[ -v array[$i] ]]
791echo two=$?
792
793
794## STDOUT:
795zero=0
796one=0
797two=1
798---
799zero=0
800one=0
801two=1
802---
803zero=0
804one=0
805two=1
806## END
807
808## N-I mksh status: 1
809## N-I mksh STDOUT:
810## END