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