1 ## compare_shells: bash
2 ## oils_failures_allowed: 1
3
4 # Var refs are done with ${!a}
5 #
6 # local/declare -n is tested in spec/named-ref.test.sh.
7 #
8 # http://stackoverflow.com/questions/16461656/bash-how-to-pass-array-as-an-argument-to-a-function
9
10 #### var ref ${!a}
11 a=b
12 b=c
13 echo ref ${!a} ${a}
14 ## stdout: ref c b
15
16 #### ${!ref-default}
17 ref=x
18 echo x=${!ref-default}
19
20 x=''
21 echo x=${!ref-default}
22
23 x=foo
24 echo x=${!ref-default}
25
26 ## STDOUT:
27 x=default
28 x=
29 x=foo
30 ## END
31
32 #### ${!undef:-}
33 # bash 4.4 gives empty string, but I feel like this could be an error
34 echo undef=${!undef-'default'}
35 echo undef=${!undef}
36
37 set -u
38 echo NOUNSET
39 echo undef=${!undef-'default'}
40 echo undef=${!undef}
41
42 ## status: 1
43 ## STDOUT:
44 ## END
45 ## OK bash STDOUT:
46 NOUNSET
47 ## END
48
49 # Bash 4.4 had been generating an empty string, but it was fixed in Bash 5.0.
50 #
51 # ## BUG bash STDOUT:
52 # undef=default
53 # undef=
54 # NOUNSET
55 # undef=default
56 # ## END
57
58 #### comparison to ${!array[@]} keys (similar SYNTAX)
59
60 declare -a a=(x y)
61 argv.py "${!a[@]}"
62 echo a_keys=$?
63
64 argv.py "${!a}" # missing [] is equivalent to ${!a[0]} ?
65 echo a_nobrackets=$?
66
67 echo ---
68 declare -A A=([A]=a [B]=b)
69
70 argv.py $(printf '%s\n' ${!A[@]} | sort)
71 echo A_keys=$?
72
73 (argv.py "${!A}") # missing [] is equivalent to ${!A[0]} ?
74 echo A_nobrackets=$?
75
76 ## STDOUT:
77 ['0', '1']
78 a_keys=0
79 ['']
80 a_nobrackets=0
81 ---
82 ['A', 'B']
83 A_keys=0
84 A_nobrackets=1
85 ## END
86
87 ## BUG bash STDOUT:
88 ['0', '1']
89 a_keys=0
90 ['']
91 a_nobrackets=0
92 ---
93 ['A', 'B']
94 A_keys=0
95 ['']
96 A_nobrackets=0
97 ## END
98
99 #### ${!a[@]-'default'} is legal but fails with more than one element
100
101 # bash allows this construct, but the indirection fails when the array has more
102 # than one element because the variable name contains a space. OSH originally
103 # made it an error unconditionally because [@] implies it's an array, so the
104 # behavior has been different from Bash when the array has a single element.
105 # We now changed it to follow Bash even when the array has a single element.
106
107 (argv.py "${!a[@]-default}")
108 echo status=$?
109
110 a=(x y z)
111 (argv.py "${!a[@]-default}")
112 echo status=$?
113 ## status: 0
114 ## STDOUT:
115 status=1
116 status=1
117 ## END
118
119 # Bash 4.4 had been generating an empty string for ${!undef[@]-}, but this was
120 # fixed in Bash 5.0.
121 #
122 # ## BUG bash status: 0
123 # ## BUG bash STDOUT:
124 # ['default']
125 # status=0
126 # status=1
127 # ## END
128
129
130 #### var ref to $@ with @
131 set -- one two
132 ref='@'
133 echo ref=${!ref}
134 ## STDOUT:
135 ref=one two
136 ## END
137
138 #### var ref to $1 and $2 with 1 and 2
139 set -- one two
140 ref1='1'
141 echo ref1=${!ref1}
142 ref2='2'
143 echo ref2=${!ref2}
144
145 ## STDOUT:
146 ref1=one
147 ref2=two
148 ## END
149
150 #### var ref: 1, @, *
151 set -- x y
152 ref=1; argv.py "${!ref}"
153 ref=@; argv.py "${!ref}"
154 ref=*; argv.py "${!ref}" # maybe_decay_array bug?
155
156 ## STDOUT:
157 ['x']
158 ['x', 'y']
159 ['x y']
160 ## END
161
162 #### var ref to special var BASH_SOURCE
163 ref='LINENO'
164 echo lineno=${!ref}
165 ## STDOUT:
166 lineno=2
167 ## END
168
169 #### var ref to $? with '?'
170 myfunc() {
171 local ref=$1
172 echo ${!ref}
173 }
174 myfunc FUNCNAME
175 myfunc '?'
176 ## STDOUT:
177 myfunc
178 0
179 ## END
180
181
182 #### Var ref, then assignment with ${ := }
183 z=zz
184 zz=
185 echo ${!z:=foo}
186 echo ${!z:=bar}
187 ## STDOUT:
188 foo
189 foo
190 ## END
191
192 #### Var ref, then error with ${ ? }
193 w=ww
194 ww=
195 echo ${!w:?'my message'}
196 echo done
197 ## status: 1
198 ## STDOUT:
199 ## END
200
201 #### Indirect expansion, THEN suffix operators
202
203 check_eq() {
204 [ "$1" = "$2" ] || { echo "$1 vs $2"; }
205 }
206 check_expand() {
207 val=$(eval "echo \"$1\"")
208 [ "$val" = "$2" ] || { echo "$1 -> expected $2, got $val"; }
209 }
210 check_err() {
211 e="$1"
212 msg=$(eval "$e" 2>&1) && echo "bad success: $e"
213 if test -n "$2"; then
214 if [[ "$msg" != $2 ]]; then
215 echo "Expected error: $e"
216 echo "Got error : $msg"
217 fi
218 fi
219 }
220 # Nearly everything in manual section 3.5.3 "Shell Parameter Expansion"
221 # is allowed after a !-indirection.
222 #
223 # Not allowed: any further prefix syntax.
224 x=xx; xx=aaabcc
225 xd=x
226 check_err '${!!xd}'
227 check_err '${!!x*}'
228 a=(asdf x)
229 check_err '${!!a[*]}'
230 check_err '${!#x}'
231 check_err '${!#a[@]}'
232 # And an array reference binds tighter in the syntax, so goes first;
233 # there's no way to spell "indirection, then array reference".
234 check_expand '${!a[1]}' xx
235 b=(aoeu a)
236 check_expand '${!b[1]}' asdf # i.e. like !(b[1]), not (!b)[1]
237 #
238 # Allowed: apparently everything else.
239 y=yy; yy=
240 check_expand '${!y:-foo}' foo
241 check_expand '${!x:-foo}' aaabcc
242
243 check_expand '${!x:?oops}' aaabcc
244
245 check_expand '${!y:+foo}' ''
246 check_expand '${!x:+foo}' foo
247
248 check_expand '${!x:2}' abcc
249 check_expand '${!x:2:2}' ab
250
251 check_expand '${!x#*a}' aabcc
252 check_expand '${!x%%c*}' aaab
253 check_expand '${!x/a*b/d}' dcc
254
255 # ^ operator not fully implemented in OSH
256 #check_expand '${!x^a}' Aaabcc
257
258 p=pp; pp='\$ '
259 check_expand '${!p@P}' '$ '
260 echo ok
261 ## stdout: ok
262
263 #### var ref OF array var -- silent a[0] decay
264 declare -a a=(ale bean)
265 echo first=${!a}
266
267 ale=zzz
268 echo first=${!a}
269
270 ## status: 0
271 ## STDOUT:
272 first=
273 first=zzz
274 ## END
275
276 #### array ref
277
278 declare -a array=(ale bean)
279 ref='array[0]'
280 echo ${!ref}
281 ## status: 0
282 ## STDOUT:
283 ale
284 ## END
285
286 #### array ref with strict_array
287 shopt -s strict_array
288
289 declare -a array=(ale bean)
290 ref='array'
291 echo ${!ref}
292 ## status: 1
293 ## stdout-json: ""
294 ## N-I bash status: 0
295 ## N-I bash STDOUT:
296 ale
297 ## END
298
299 #### var ref TO array var
300 shopt -s compat_array
301
302 declare -a array=(ale bean)
303
304 ref='array' # when compat_array is on, this is like array[0]
305 ref_AT='array[@]'
306
307 echo ${!ref}
308 echo ${!ref_AT}
309
310 ## STDOUT:
311 ale
312 ale bean
313 ## END
314
315 #### var ref TO array var, with subscripts
316 f() {
317 argv.py "${!1}"
318 }
319 f 'nonexistent[0]'
320 array=(x y z)
321 f 'array[0]'
322 f 'array[1+1]'
323 f 'array[@]'
324 f 'array[*]'
325 # Also associative arrays.
326 ## STDOUT:
327 ['']
328 ['x']
329 ['z']
330 ['x', 'y', 'z']
331 ['x y z']
332 ## END
333
334 #### var ref TO assoc array a[key]
335 shopt -s compat_array
336
337 declare -A assoc=([ale]=bean [corn]=dip)
338 ref=assoc
339 #ref_AT='assoc[@]'
340
341 # UNQUOTED doesn't work with Oil's parser
342 #ref_SUB='assoc[ale]'
343 ref_SUB='assoc["ale"]'
344
345 ref_SUB_QUOTED='assoc["al"e]'
346
347 ref_SUB_BAD='assoc["bad"]'
348
349 echo ref=${!ref} # compat_array: assoc is equivalent to assoc[0]
350 #echo ref_AT=${!ref_AT}
351 echo ref_SUB=${!ref_SUB}
352 echo ref_SUB_QUOTED=${!ref_SUB_QUOTED}
353 echo ref_SUB_BAD=${!ref_SUB_BAD}
354
355 ## STDOUT:
356 ref=
357 ref_SUB=bean
358 ref_SUB_QUOTED=bean
359 ref_SUB_BAD=
360 ## END
361
362 #### var ref TO array with arbitrary subscripts
363 shopt -s eval_unsafe_arith compat_array
364
365 f() {
366 local val=$(echo "${!1}")
367 if test "$val" = y; then
368 echo "works: $1"
369 fi
370 }
371 # Warmup: nice plain array reference
372 a=(x y)
373 f 'a[1]'
374 #
375 # Not allowed:
376 # no brace expansion
377 f 'a[{1,0}]' # operand expected
378 # no process substitution (but see command substitution below!)
379 f 'a[<(echo x)]' # operand expected
380 # TODO word splitting seems interesting
381 aa="1 0"
382 f 'a[$aa]' # 1 0: syntax error in expression (error token is "0")
383 # no filename globbing
384 f 'a[b*]' # operand expected
385 f 'a[1"]' # bad substitution
386 #
387 # Allowed: most everything else in section 3.5 "Shell Expansions".
388 # shell parameter expansion
389 b=1
390 f 'a[$b]'
391 f 'a[${c:-1}]'
392 # (... and presumably most of the other features there)
393 # command substitution, yikes!
394 f 'a[$(echo 1)]'
395 # arithmetic expansion
396 f 'a[$(( 3 - 2 ))]'
397
398 # All of these are undocumented and probably shouldn't exist,
399 # though it's always possible some will turn up in the wild and
400 # we'll end up implementing them.
401
402 ## STDOUT:
403 works: a[1]
404 works: a[$b]
405 works: a[${c:-1}]
406 works: a[$(echo 1)]
407 works: a[$(( 3 - 2 ))]
408 ## END
409
410 #### Bizarre tilde expansion in array index
411 a=(x y)
412 PWD=1
413 ref='a[~+]'
414 echo ${!ref}
415 ## status: 1
416
417 # Bash 4.4 had a bug, which was fixed in Bash 5.0.
418 #
419 # ## BUG bash status: 0
420 # ## BUG bash STDOUT:
421 # y
422 # ## END
423
424 #### Indirect expansion TO fancy expansion features bash disallows
425
426 check_indir() {
427 result="${!1}"
428 desugared_result=$(eval 'echo "${'"$1"'}"')
429 [ "$2" = "$desugared_result" ] || { echo "$1 $desugared_result"; }
430 }
431 x=y
432 y=a
433 a=(x y)
434 declare -A aa
435 aa=([k]=r [l]=s)
436 # malformed array indexing
437 check_indir "a[0"
438 check_indir "aa[k"
439 # double indirection
440 check_indir "!x" a
441 check_indir "!a[0]" y
442 # apparently everything else in the manual under "Shell Parameter Expansion"
443 check_indir "x:-foo" y
444 check_indir "x:=foo" y
445 check_indir "x:?oops" y
446 check_indir "x:+yy" yy
447 check_indir "x:0" y
448 check_indir "x:0:1" y
449 check_indir "!a@" "a aa"
450 # (!a[@] is elsewhere)
451 check_indir "#x" 1
452 check_indir "x#y"
453 check_indir "x/y/foo" foo
454 check_indir "x@Q" "'y'"
455 echo done
456 ## status: 1
457 ## stdout-json: ""
458 ## OK bash status: 0
459 ## OK bash stdout: done
460
461 #### Bad var ref
462 a='bad var name'
463 echo ref ${!a}
464 echo status=$?
465
466 ## STDOUT:
467 status=1
468 ## END
469 ## OK osh stdout-json: ""
470 ## OK osh status: 1
471
472 #### Bad var ref 2
473 b='/' # really bad
474 echo ref ${!b}
475 echo status=$?
476 ## STDOUT:
477 status=1
478 ## END
479 ## OK osh stdout-json: ""
480 ## OK osh status: 1
481
482 #### ${!OPTIND} (used by bash completion
483 set -- a b c
484 echo ${!OPTIND}
485 f() {
486 local OPTIND=1
487 echo ${!OPTIND}
488 local OPTIND=2
489 echo ${!OPTIND}
490 }
491 f x y z
492 ## STDOUT:
493 a
494 x
495 y
496 ## END
497
498 #### var ref doesn't need cycle detection
499 x=y
500 y=x
501 echo cycle=${!x}
502
503 typeset -n a=b
504 typeset -n b=a
505 echo cycle=${a}
506 ## status: 1
507 ## STDOUT:
508 cycle=x
509 ## END
510 ## OK bash status: 0
511 ## OK bash STDOUT:
512 cycle=x
513 cycle=
514 ## END
515
516 #### Var Ref Code Injection $(tee PWNED)
517
518 typeset -a a
519 a=(42)
520
521 x='a[$(echo 0 | tee PWNED)]'
522
523 echo ${!x}
524
525 if test -f PWNED; then
526 echo PWNED
527 cat PWNED
528 else
529 echo NOPE
530 fi
531
532 ## status: 1
533 ## STDOUT:
534 ## END
535
536 ## BUG bash status: 0
537 ## BUG bash STDOUT:
538 42
539 PWNED
540 0
541 ## END
542
543 #### ${!array_ref:-set} and ${!array_ref:=assign}
544
545 ref='a[@]'
546 a=('' '' '')
547
548 echo "==== check ===="
549
550 argv.py "${!ref:-set}"
551 argv.py "${a[@]:-set}"
552
553 echo "==== assign ===="
554
555 argv.py "${!ref:=assign}"
556 argv.py "${!ref}"
557 a=('' '' '') # revert the state in case it is modified
558
559 argv.py "${a[@]:=assign}"
560 argv.py "${a[@]}"
561
562 ## STDOUT:
563 ==== check ====
564 ['', '', '']
565 ['', '', '']
566 ==== assign ====
567 ['', '', '']
568 ['', '', '']
569 ['', '', '']
570 ['', '', '']
571 ## END
572
573 #### Array indirect expansion with suffix operators
574
575 declare -A ref=(['dummy']=v1)
576 function test-suffixes {
577 echo "==== $1 ===="
578 ref['dummy']=$1
579 argv.py "${!ref[@]:2}"
580 argv.py "${!ref[@]:1:2}"
581 argv.py "${!ref[@]:-empty}"
582 argv.py "${!ref[@]:+set}"
583 argv.py "${!ref[@]:=assign}"
584 }
585
586 v1=value
587 test-suffixes v1
588 echo "v1=$v1"
589
590 v2=
591 test-suffixes v2
592 echo "v2=$v2"
593
594 a1=()
595 test-suffixes a1
596 argv.py "${a1[@]}"
597
598 a2=(element)
599 test-suffixes 'a2[0]'
600 argv.py "${a2[@]}"
601
602 a3=(1 2 3)
603 test-suffixes 'a3[@]'
604 argv.py "${a3[@]}"
605
606 ## STDOUT:
607 ==== v1 ====
608 ['lue']
609 ['al']
610 ['value']
611 ['set']
612 ['value']
613 v1=value
614 ==== v2 ====
615 ['']
616 ['']
617 ['empty']
618 ['']
619 ['assign']
620 v2=assign
621 ==== a1 ====
622 ['']
623 ['']
624 ['empty']
625 ['']
626 ['assign']
627 ['assign']
628 ==== a2[0] ====
629 ['ement']
630 ['le']
631 ['element']
632 ['set']
633 ['element']
634 ['element']
635 ==== a3[@] ====
636 ['3']
637 ['2', '3']
638 ['1', '2', '3']
639 ['set']
640 ['1', '2', '3']
641 ['1', '2', '3']
642 ## END
643
644 #### Array indirect expansion with replacements
645
646 declare -A ref=(['dummy']=v1)
647 function test-rep {
648 echo "==== $1 ===="
649 ref['dummy']=$1
650 argv.py "${!ref[@]#?}"
651 argv.py "${!ref[@]%?}"
652 argv.py "${!ref[@]//[a-f]}"
653 argv.py "${!ref[@]//[a-f]/x}"
654 }
655
656 v1=value
657 test-rep v1
658
659 v2=
660 test-rep v2
661
662 a1=()
663 test-rep a1
664
665 a2=(element)
666 test-rep 'a2[0]'
667
668 a3=(1 2 3)
669 test-rep 'a3[@]'
670
671 ## STDOUT:
672 ==== v1 ====
673 ['alue']
674 ['valu']
675 ['vlu']
676 ['vxlux']
677 ==== v2 ====
678 ['']
679 ['']
680 ['']
681 ['']
682 ==== a1 ====
683 ['']
684 ['']
685 ['']
686 ['']
687 ==== a2[0] ====
688 ['lement']
689 ['elemen']
690 ['lmnt']
691 ['xlxmxnt']
692 ==== a3[@] ====
693 ['', '', '']
694 ['', '', '']
695 ['1', '2', '3']
696 ['1', '2', '3']
697 ## END
698
699 #### Array indirect expansion with @? conversion
700
701 declare -A ref=(['dummy']=v1)
702 function test-op0 {
703 echo "==== $1 ===="
704 ref['dummy']=$1
705 argv.py "${!ref[@]@Q}"
706 argv.py "${!ref[@]@P}"
707 argv.py "${!ref[@]@a}"
708 }
709
710 v1=value
711 test-op0 v1
712
713 v2=
714 test-op0 v2
715
716 a1=()
717 test-op0 a1
718
719 a2=(element)
720 test-op0 'a2[0]'
721
722 a3=(1 2 3)
723 test-op0 'a3[@]'
724
725 ## STDOUT:
726 ==== v1 ====
727 ['value']
728 ['value']
729 ['']
730 ==== v2 ====
731 ["''"]
732 ['']
733 ['']
734 ==== a1 ====
735 ['']
736 ['']
737 ['a']
738 ==== a2[0] ====
739 ['element']
740 ['element']
741 ['a']
742 ==== a3[@] ====
743 ['1', '2', '3']
744 ['1', '2', '3']
745 ['a', 'a', 'a']
746 ## END
747
748 # Bash 4.4 had a bug in the section "==== a3[@] ====":
749 #
750 # ==== a3[@] ====
751 # []
752 # []
753 # []
754
755 ## BUG bash STDOUT:
756 ==== v1 ====
757 ["'value'"]
758 ['value']
759 ['']
760 ==== v2 ====
761 ["''"]
762 ['']
763 ['']
764 ==== a1 ====
765 ['']
766 ['']
767 ['a']
768 ==== a2[0] ====
769 ["'element'"]
770 ['element']
771 ['a']
772 ==== a3[@] ====
773 ["'1'", "'2'", "'3'"]
774 ['1', '2', '3']
775 ['a', 'a', 'a']
776 ## END