OILS / spec / array.test.sh View on Github | oils.pub

1011 lines, 471 significant
1## compare_shells: bash mksh
2## oils_failures_allowed: 2
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# 2025-01 We changed OSH.
389
390single=('')
391argv.py ${single[@]:-none} x "${single[@]:-none}"
392## stdout: ['none', 'x', 'none']
393
394#### Stripping a whole array unquoted
395# Problem: it joins it first.
396files=('foo.c' 'sp ace.h' 'bar.c')
397argv.py ${files[@]%.c}
398## status: 0
399## stdout: ['foo', 'sp', 'ace.h', 'bar']
400## N-I mksh status: 1
401## N-I mksh stdout-json: ""
402
403#### Stripping a whole array quoted
404files=('foo.c' 'sp ace.h' 'bar.c')
405argv.py "${files[@]%.c}"
406## status: 0
407## stdout: ['foo', 'sp ace.h', 'bar']
408## N-I mksh status: 1
409## N-I mksh stdout-json: ""
410
411#### Multiple subscripts not allowed
412# NOTE: bash 4.3 had a bug where it ignored the bad subscript, but now it is
413# fixed.
414a=('123' '456')
415argv.py "${a[0]}" "${a[0][0]}"
416## stdout-json: ""
417## status: 2
418## OK bash/mksh status: 1
419
420#### Length op, index op, then transform op is not allowed
421a=('123' '456')
422echo "${#a[0]}" "${#a[0]/1/xxx}"
423## stdout-json: ""
424## status: 2
425## OK bash/mksh status: 1
426
427#### ${mystr[@]} and ${mystr[*]} are no-ops
428s='abc'
429echo ${s[@]}
430echo ${s[*]}
431## STDOUT:
432abc
433abc
434## END
435
436#### ${mystr[@]} and ${mystr[*]} disallowed with strict_array
437
438$SH -c 'shopt -s strict_array; s="abc"; echo ${s[@]}'
439echo status=$?
440
441$SH -c 'shopt -s strict_array; s="abc"; echo ${s[*]}'
442echo status=$?
443
444## status: 0
445## STDOUT:
446status=1
447status=1
448## END
449## N-I bash/mksh STDOUT:
450abc
451status=0
452abc
453status=0
454## END
455
456#### Create a "user" array out of the argv array
457set -- 'a b' 'c'
458array1=('x y' 'z')
459array2=("$@")
460argv.py "${array1[@]}" "${array2[@]}"
461## stdout: ['x y', 'z', 'a b', 'c']
462
463#### Tilde expansion within array
464HOME=/home/bob
465a=(~/src ~/git)
466echo "${a[@]}"
467## stdout: /home/bob/src /home/bob/git
468
469#### Brace Expansion within Array
470a=(-{a,b} {c,d}-)
471echo "${a[@]}"
472## stdout: -a -b c- d-
473
474#### array default
475default=('1 2' '3')
476argv.py "${undef[@]:-${default[@]}}"
477## stdout: ['1 2', '3']
478
479#### Singleton Array Copy and Assign. OSH can't index strings with ints
480a=( '12 3' )
481b=( "${a[@]}" )
482c="${a[@]}" # This decays it to a string
483d=${a[*]} # This decays it to a string
484echo ${#a[0]} ${#b[0]}
485echo ${#a[@]} ${#b[@]}
486
487# osh is intentionally stricter, and these fail.
488echo ${#c[0]} ${#d[0]}
489echo ${#c[@]} ${#d[@]}
490
491## status: 1
492## STDOUT:
4934 4
4941 1
495## END
496## OK bash/mksh status: 0
497## OK bash/mksh STDOUT:
4984 4
4991 1
5004 4
5011 1
502## END
503
504#### declare -a / local -a is empty array
505declare -a myarray
506argv.py "${myarray[@]}"
507myarray+=('x')
508argv.py "${myarray[@]}"
509
510f() {
511 local -a myarray
512 argv.py "${myarray[@]}"
513 myarray+=('x')
514 argv.py "${myarray[@]}"
515}
516f
517## STDOUT:
518[]
519['x']
520[]
521['x']
522## END
523
524#### Create sparse array
525a=()
526(( a[99]=1 )) # osh doesn't parse index assignment outside arithmetic yet
527echo len=${#a[@]}
528argv.py "${a[@]}"
529echo "unset=${a[33]}"
530echo len-of-unset=${#a[33]}
531## STDOUT:
532len=1
533['1']
534unset=
535len-of-unset=0
536## END
537
538#### Create sparse array implicitly
539(( a[99]=1 ))
540echo len=${#a[@]}
541argv.py "${a[@]}"
542echo "unset=${a[33]}"
543echo len-of-unset=${#a[33]}
544## STDOUT:
545len=1
546['1']
547unset=
548len-of-unset=0
549## END
550
551#### Append sparse arrays
552a=()
553(( a[99]=1 ))
554b=()
555(( b[33]=2 ))
556(( b[66]=3 ))
557a+=( "${b[@]}" )
558argv.py "${a[@]}"
559argv.py "${a[99]}" "${a[100]}" "${a[101]}"
560## STDOUT:
561['1', '2', '3']
562['1', '2', '3']
563## END
564
565#### Slice of sparse array with [@]
566# mksh doesn't support this syntax! It's a bash extension.
567(( a[33]=1 ))
568(( a[66]=2 ))
569(( a[99]=2 ))
570argv.py "${a[@]:15:2}"
571## stdout: ['1', '2']
572## N-I mksh status: 1
573## N-I mksh stdout-json: ""
574
575#### Using an array itself as the index on LHS
576shopt -u strict_arith
577a[a]=42
578a[a]=99
579argv.py "${a[@]}" "${a[0]}" "${a[42]}" "${a[99]}"
580
581## status: 0
582## STDOUT:
583['42', '99', '42', '99', '']
584## END
585
586#### Using an array itself as the index on RHS
587shopt -u strict_arith
588a=(1 2 3)
589(( x = a[a] ))
590echo $x
591## status: 0
592## STDOUT:
5932
594## END
595
596#### a[$x$y] on LHS and RHS
597x=1
598y=2
599a[$x$y]=foo
600
601# not allowed by OSH parsing
602#echo ${a[$x$y]}
603
604echo ${a[12]}
605echo ${#a[@]}
606
607## STDOUT:
608foo
6091
610## END
611
612
613#### Dynamic parsing of LHS a[$code]=value
614
615declare -a array
616array[x=1]='one'
617
618code='y=2'
619#code='1+2' # doesn't work either
620array[$code]='two'
621
622argv.py "${array[@]}"
623echo x=$x
624echo y=$y
625
626## STDOUT:
627['one', 'two']
628x=1
629y=2
630## END
631## N-I dash stdout-json: ""
632## N-I dash status: 2
633
634#### Dynamic parsing of RHS ${a[$code]}
635declare -a array
636array=(zero one two three)
637
638echo ${array[1+2]}
639
640code='1+2'
641echo ${array[$code]}
642
643## STDOUT:
644three
645three
646## END
647
648# it still dynamically parses
649
650## OK zsh STDOUT:
651two
652two
653## END
654
655
656#### Is element set? test -v a[i]
657
658# note: modern versions of zsh implement this
659
660array=(1 2 3 '')
661
662test -v 'array[1]'
663echo set=$?
664
665test -v 'array[3]'
666echo empty=$?
667
668test -v 'array[4]'
669echo unset=$?
670
671## STDOUT:
672set=0
673empty=0
674unset=1
675## END
676
677## N-I mksh STDOUT:
678set=2
679empty=2
680unset=2
681## END
682
683
684#### [[ -v a[i] ]]
685
686# note: modern versions of zsh implement this
687
688array=(1 2 3)
689[[ -v array[1] ]]
690echo status=$?
691
692[[ -v array[4] ]]
693echo status=$?
694
695## STDOUT:
696status=0
697status=1
698## END
699
700## N-I mksh status: 1
701## N-I mksh STDOUT:
702## END
703
704
705#### test -v a[i] with arith expressions
706
707array=(1 2 3 '')
708
709test -v 'array[1+1]'
710echo status=$?
711
712test -v 'array[4+1]'
713echo status=$?
714
715echo
716echo dbracket
717
718[[ -v array[1+1] ]]
719echo status=$?
720
721[[ -v array[4+1] ]]
722echo status=$?
723
724## STDOUT:
725status=0
726status=1
727
728dbracket
729status=0
730status=1
731## END
732
733## N-I mksh status: 1
734## N-I mksh STDOUT:
735status=2
736status=2
737
738dbracket
739## END
740
741
742#### More arith expressions in [[ -v array[expr]] ]]
743
744typeset -a array
745array=('' nonempty)
746
747# This feels inconsistent with the rest of bash?
748zero=0
749
750[[ -v array[zero+0] ]]
751echo zero=$?
752
753[[ -v array[zero+1] ]]
754echo one=$?
755
756[[ -v array[zero+2] ]]
757echo two=$?
758
759echo ---
760
761i='0+0'
762[[ -v array[i] ]]
763echo zero=$?
764
765i='0+1'
766[[ -v array[i] ]]
767echo one=$?
768
769i='0+2'
770[[ -v array[i] ]]
771echo two=$?
772
773echo ---
774
775i='0+0'
776[[ -v array[$i] ]]
777echo zero=$?
778
779i='0+1'
780[[ -v array[$i] ]]
781echo one=$?
782
783i='0+2'
784[[ -v array[$i] ]]
785echo two=$?
786
787
788## STDOUT:
789zero=0
790one=0
791two=1
792---
793zero=0
794one=0
795two=1
796---
797zero=0
798one=0
799two=1
800## END
801
802## N-I mksh status: 1
803## N-I mksh STDOUT:
804## END
805
806
807#### Regression: Assigning with out-of-range negative index
808a=()
809a[-1]=1
810
811## status: 1
812## STDOUT:
813## END
814## STDERR:
815 a[-1]=1
816 ^~
817[ stdin ]:2: fatal: Index -1 is out of bounds for array of length 0
818## END
819
820## OK bash STDERR:
821bash: line 2: a[-1]: bad array subscript
822## END
823
824# Note: mksh interprets -1 as 0xFFFFFFFF
825## N-I mksh status: 0
826## N-I mksh STDERR:
827## END
828
829
830#### Regression: Negative index in [[ -v a[index] ]]
831a[0]=x
832a[5]=y
833a[10]=z
834[[ -v a[-1] ]] && echo 'a has -1'
835[[ -v a[-2] ]] && echo 'a has -2'
836[[ -v a[-5] ]] && echo 'a has -5'
837[[ -v a[-6] ]] && echo 'a has -6'
838[[ -v a[-10] ]] && echo 'a has -10'
839[[ -v a[-11] ]] && echo 'a has -11'
840
841## STDOUT:
842a has -1
843a has -6
844a has -11
845## END
846
847## N-I mksh status: 1
848## N-I mksh STDOUT:
849## END
850
851
852#### Regression: Negative out-of-range index in [[ -v a[index] ]]
853e=()
854[[ -v e[-1] ]] && echo 'e has -1'
855
856## status: 1
857## STDERR:
858 [[ -v e[-1] ]] && echo 'e has -1'
859 ^
860[ stdin ]:2: fatal: -v got index -1, which is out of bounds for array of length 0
861## END
862
863## OK bash STDERR:
864bash: line 2: e: bad array subscript
865## END
866
867## N-I mksh STDERR:
868mksh: <stdin>[2]: syntax error: 'e[-1]' unexpected operator/operand
869## END
870
871
872#### a+=() modifies existing instance of BashArray
873case $SH in mksh|bash) exit ;; esac
874
875a=(1 2 3)
876var b = a
877a+=(4 5)
878echo "a=(${a[*]})"
879echo "b=(${b[*]})"
880
881## STDOUT:
882a=(1 2 3 4 5)
883b=(1 2 3 4 5)
884## END
885
886## N-I mksh/bash STDOUT:
887## END
888
889
890#### Regression: unset a[-2]: out-of-bound negative index should cause error
891case $SH in mksh) exit ;; esac
892
893a=(1)
894unset -v 'a[-2]'
895
896## status: 1
897## STDOUT:
898## END
899## STDERR:
900 unset -v 'a[-2]'
901 ^
902[ stdin ]:4: a[-2]: Index is out of bounds for array of length 1
903## END
904
905## OK bash STDERR:
906bash: line 4: unset: [-2]: bad array subscript
907## END
908
909## N-I mksh status: 0
910## N-I mksh STDERR:
911## END
912
913
914#### Regression: Out-of-bound negative offset for ${a[@]:offset}
915case $SH in mksh) exit ;; esac
916
917a=(1 2 3 4)
918echo "a=(${a[*]})"
919echo "begin=-1 -> (${a[*]: -1})"
920echo "begin=-2 -> (${a[*]: -2})"
921echo "begin=-3 -> (${a[*]: -3})"
922echo "begin=-4 -> (${a[*]: -4})"
923echo "begin=-5 -> (${a[*]: -5})"
924
925## STDOUT:
926a=(1 2 3 4)
927begin=-1 -> (4)
928begin=-2 -> (3 4)
929begin=-3 -> (2 3 4)
930begin=-4 -> (1 2 3 4)
931begin=-5 -> ()
932## END
933
934## N-I mksh STDOUT:
935## END
936
937
938#### Regression: Array length after unset
939case $SH in mksh) exit ;; esac
940
941a=(x)
942a[9]=y
943echo "len ${#a[@]};"
944
945unset -v 'a[-1]'
946echo "len ${#a[@]};"
947echo "last ${a[@]: -1};"
948
949## STDOUT:
950len 2;
951len 1;
952last x;
953## END
954
955## N-I mksh STDOUT:
956## END
957
958
959#### Regression: ${a[@]@Q} crash with `a[0]=x a[2]=y`
960case $SH in mksh) exit ;; esac
961
962a[0]=x
963a[2]=y
964echo "quoted = (${a[@]@Q})"
965
966## STDOUT:
967quoted = (x y)
968## END
969
970## OK bash STDOUT:
971quoted = ('x' 'y')
972## END
973
974## N-I mksh STDOUT:
975## END
976
977
978#### Regression: silent out-of-bound negative index in ${a[-2]} and $((a[-2]))
979case $SH in mksh) exit ;; esac
980
981a=(x)
982echo "[${a[-2]}]"
983echo $?
984echo "[$((a[-2]))]"
985echo $?
986
987## STDOUT:
988[]
9890
990[0]
9910
992## END
993## STDERR:
994 echo "[${a[-2]}]"
995 ^
996[ stdin ]:4: Index -2 out of bounds for array of length 1
997 echo "[$((a[-2]))]"
998 ^
999[ stdin ]:6: Index -2 out of bounds for array of length 1
1000## END
1001
1002## OK bash STDERR:
1003bash: line 4: a: bad array subscript
1004bash: line 6: a: bad array subscript
1005## END
1006
1007## N-I mksh status: 0
1008## N-I mksh STDOUT:
1009## END
1010## N-I mksh STDERR:
1011## END