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