OILS / spec / var-ref.test.sh View on Github | oils.pub

769 lines, 464 significant
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}
12a=b
13b=c
14echo ref ${!a} ${a}
15## stdout: ref c b
16
17#### ${!ref-default}
18ref=x
19echo x=${!ref-default}
20
21x=''
22echo x=${!ref-default}
23
24x=foo
25echo x=${!ref-default}
26
27## STDOUT:
28x=default
29x=
30x=foo
31## END
32
33#### ${!undef:-}
34# bash 4.4 gives empty string, but I feel like this could be an error
35echo undef=${!undef-'default'}
36echo undef=${!undef}
37
38set -u
39echo NOUNSET
40echo undef=${!undef-'default'}
41echo undef=${!undef}
42
43## status: 1
44## STDOUT:
45NOUNSET
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
59declare -a a=(x y)
60argv.py "${!a[@]}"
61echo a_keys=$?
62
63argv.py "${!a}" # missing [] is equivalent to ${!a[0]} ?
64echo a_nobrackets=$?
65
66echo ---
67declare -A A=([A]=a [B]=b)
68
69argv.py $(printf '%s\n' ${!A[@]} | sort)
70echo A_keys=$?
71
72(argv.py "${!A}") # missing [] is equivalent to ${!A[0]} ?
73echo A_nobrackets=$?
74
75## STDOUT:
76['0', '1']
77a_keys=0
78['']
79a_nobrackets=0
80---
81['A', 'B']
82A_keys=0
83A_nobrackets=1
84## END
85
86## BUG bash STDOUT:
87['0', '1']
88a_keys=0
89['']
90a_nobrackets=0
91---
92['A', 'B']
93A_keys=0
94['']
95A_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}")
107echo status=$?
108
109a=(x y z)
110(argv.py "${!a[@]-default}")
111echo status=$?
112## status: 0
113## STDOUT:
114status=1
115status=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 @
130set -- one two
131ref='@'
132echo ref=${!ref}
133## STDOUT:
134ref=one two
135## END
136
137#### var ref to $1 and $2 with 1 and 2
138set -- one two
139ref1='1'
140echo ref1=${!ref1}
141ref2='2'
142echo ref2=${!ref2}
143
144## STDOUT:
145ref1=one
146ref2=two
147## END
148
149#### var ref: 1, @, *
150set -- x y
151ref=1; argv.py "${!ref}"
152ref=@; argv.py "${!ref}"
153ref=*; 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
162ref='LINENO'
163echo lineno=${!ref}
164## STDOUT:
165lineno=2
166## END
167
168#### var ref to $? with '?'
169myfunc() {
170 local ref=$1
171 echo ${!ref}
172}
173myfunc FUNCNAME
174myfunc '?'
175## STDOUT:
176myfunc
1770
178## END
179
180
181#### Var ref, then assignment with ${ := }
182z=zz
183zz=
184echo ${!z:=foo}
185echo ${!z:=bar}
186## STDOUT:
187foo
188foo
189## END
190
191#### Var ref, then error with ${ ? }
192w=ww
193ww=
194echo ${!w:?'my message'}
195echo done
196## status: 1
197## STDOUT:
198## END
199
200#### Indirect expansion, THEN suffix operators
201
202check_eq() {
203 [ "$1" = "$2" ] || { echo "$1 vs $2"; }
204}
205check_expand() {
206 val=$(eval "echo \"$1\"")
207 [ "$val" = "$2" ] || { echo "$1 -> expected $2, got $val"; }
208}
209check_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.
223x=xx; xx=aaabcc
224xd=x
225check_err '${!!xd}'
226check_err '${!!x*}'
227a=(asdf x)
228check_err '${!!a[*]}'
229check_err '${!#x}'
230check_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".
233check_expand '${!a[1]}' xx
234b=(aoeu a)
235check_expand '${!b[1]}' asdf # i.e. like !(b[1]), not (!b)[1]
236#
237# Allowed: apparently everything else.
238y=yy; yy=
239check_expand '${!y:-foo}' foo
240check_expand '${!x:-foo}' aaabcc
241
242check_expand '${!x:?oops}' aaabcc
243
244check_expand '${!y:+foo}' ''
245check_expand '${!x:+foo}' foo
246
247check_expand '${!x:2}' abcc
248check_expand '${!x:2:2}' ab
249
250check_expand '${!x#*a}' aabcc
251check_expand '${!x%%c*}' aaab
252check_expand '${!x/a*b/d}' dcc
253
254# ^ operator not fully implemented in OSH
255#check_expand '${!x^a}' Aaabcc
256
257p=pp; pp='\$ '
258check_expand '${!p@P}' '$ '
259echo ok
260## stdout: ok
261
262#### var ref OF array var -- silent a[0] decay
263declare -a a=(ale bean)
264echo first=${!a}
265
266ale=zzz
267echo first=${!a}
268
269## status: 0
270## STDOUT:
271first=
272first=zzz
273## END
274
275#### array ref
276
277declare -a array=(ale bean)
278ref='array[0]'
279echo ${!ref}
280## status: 0
281## STDOUT:
282ale
283## END
284
285#### array ref with strict_array
286shopt -s strict_array
287
288declare -a array=(ale bean)
289ref='array'
290echo ${!ref}
291## status: 1
292## stdout-json: ""
293## N-I bash status: 0
294## N-I bash STDOUT:
295ale
296## END
297
298#### var ref TO array var
299shopt -s compat_array
300
301declare -a array=(ale bean)
302
303ref='array' # when compat_array is on, this is like array[0]
304ref_AT='array[@]'
305
306echo ${!ref}
307echo ${!ref_AT}
308
309## STDOUT:
310ale
311ale bean
312## END
313
314#### var ref TO array var, with subscripts
315f() {
316 argv.py "${!1}"
317}
318f 'nonexistent[0]'
319array=(x y z)
320f 'array[0]'
321f 'array[1+1]'
322f 'array[@]'
323f '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]
334shopt -s compat_array
335
336declare -A assoc=([ale]=bean [corn]=dip)
337ref=assoc
338#ref_AT='assoc[@]'
339
340# UNQUOTED doesn't work with Oil's parser
341#ref_SUB='assoc[ale]'
342ref_SUB='assoc["ale"]'
343
344ref_SUB_QUOTED='assoc["al"e]'
345
346ref_SUB_BAD='assoc["bad"]'
347
348echo ref=${!ref} # compat_array: assoc is equivalent to assoc[0]
349#echo ref_AT=${!ref_AT}
350echo ref_SUB=${!ref_SUB}
351echo ref_SUB_QUOTED=${!ref_SUB_QUOTED}
352echo ref_SUB_BAD=${!ref_SUB_BAD}
353
354## STDOUT:
355ref=
356ref_SUB=bean
357ref_SUB_QUOTED=bean
358ref_SUB_BAD=
359## END
360
361#### var ref TO array with arbitrary subscripts
362shopt -s eval_unsafe_arith compat_array
363
364f() {
365 local val=$(echo "${!1}")
366 if test "$val" = y; then
367 echo "works: $1"
368 fi
369}
370# Warmup: nice plain array reference
371a=(x y)
372f 'a[1]'
373#
374# Not allowed:
375# no brace expansion
376f 'a[{1,0}]' # operand expected
377# no process substitution (but see command substitution below!)
378f 'a[<(echo x)]' # operand expected
379# TODO word splitting seems interesting
380aa="1 0"
381f 'a[$aa]' # 1 0: syntax error in expression (error token is "0")
382# no filename globbing
383f 'a[b*]' # operand expected
384f 'a[1"]' # bad substitution
385#
386# Allowed: most everything else in section 3.5 "Shell Expansions".
387# shell parameter expansion
388b=1
389f 'a[$b]'
390f 'a[${c:-1}]'
391# (... and presumably most of the other features there)
392# command substitution, yikes!
393f 'a[$(echo 1)]'
394# arithmetic expansion
395f '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:
402works: a[1]
403works: a[$b]
404works: a[${c:-1}]
405works: a[$(echo 1)]
406works: a[$(( 3 - 2 ))]
407## END
408
409#### Bizarre tilde expansion in array index
410a=(x y)
411PWD=1
412ref='a[~+]'
413echo ${!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
425check_indir() {
426 result="${!1}"
427 desugared_result=$(eval 'echo "${'"$1"'}"')
428 [ "$2" = "$desugared_result" ] || { echo "$1 $desugared_result"; }
429}
430x=y
431y=a
432a=(x y)
433declare -A aa
434aa=([k]=r [l]=s)
435# malformed array indexing
436check_indir "a[0"
437check_indir "aa[k"
438# double indirection
439check_indir "!x" a
440check_indir "!a[0]" y
441# apparently everything else in the manual under "Shell Parameter Expansion"
442check_indir "x:-foo" y
443check_indir "x:=foo" y
444check_indir "x:?oops" y
445check_indir "x:+yy" yy
446check_indir "x:0" y
447check_indir "x:0:1" y
448check_indir "!a@" "a aa"
449# (!a[@] is elsewhere)
450check_indir "#x" 1
451check_indir "x#y"
452check_indir "x/y/foo" foo
453check_indir "x@Q" y
454echo done
455## status: 0
456## stdout: done
457
458#### Bad var ref
459a='bad var name'
460echo ref ${!a}
461echo status=$?
462
463## STDOUT:
464status=1
465## END
466
467#### Bad var ref 2
468b='/' # really bad
469echo ref ${!b}
470echo status=$?
471## STDOUT:
472status=1
473## END
474
475#### ${!OPTIND} (used by bash completion
476set -- a b c
477echo ${!OPTIND}
478f() {
479 local OPTIND=1
480 echo ${!OPTIND}
481 local OPTIND=2
482 echo ${!OPTIND}
483}
484f x y z
485## STDOUT:
486a
487x
488y
489## END
490
491#### var ref doesn't need cycle detection
492x=y
493y=x
494echo cycle=${!x}
495
496typeset -n a=b
497typeset -n b=a
498echo cycle=${a}
499## status: 1
500## STDOUT:
501cycle=x
502## END
503## OK bash status: 0
504## OK bash STDOUT:
505cycle=x
506cycle=
507## END
508
509#### Var Ref Code Injection $(tee PWNED)
510
511typeset -a a
512a=(42)
513
514x='a[$(echo 0 | tee PWNED)]'
515
516echo ${!x}
517
518if test -f PWNED; then
519 echo PWNED
520 cat PWNED
521else
522 echo NOPE
523fi
524
525## status: 1
526## STDOUT:
527## END
528
529## BUG bash status: 0
530## BUG bash STDOUT:
53142
532PWNED
5330
534## END
535
536#### ${!array_ref:-set} and ${!array_ref:=assign}
537
538ref='a[@]'
539a=('' '' '')
540
541echo "==== check ===="
542
543argv.py "${!ref:-set}"
544argv.py "${a[@]:-set}"
545
546echo "==== assign ===="
547
548argv.py "${!ref:=assign}"
549argv.py "${!ref}"
550a=('' '' '') # revert the state in case it is modified
551
552argv.py "${a[@]:=assign}"
553argv.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
568declare -A ref=(['dummy']=v1)
569function 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
579v1=value
580test-suffixes v1
581echo "v1=$v1"
582
583v2=
584test-suffixes v2
585echo "v2=$v2"
586
587a1=()
588test-suffixes a1
589argv.py "${a1[@]}"
590
591a2=(element)
592test-suffixes 'a2[0]'
593argv.py "${a2[@]}"
594
595a3=(1 2 3)
596test-suffixes 'a3[@]'
597argv.py "${a3[@]}"
598
599## STDOUT:
600==== v1 ====
601['lue']
602['al']
603['value']
604['set']
605['value']
606v1=value
607==== v2 ====
608['']
609['']
610['empty']
611['']
612['assign']
613v2=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
639declare -A ref=(['dummy']=v1)
640function 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
649v1=value
650test-rep v1
651
652v2=
653test-rep v2
654
655a1=()
656test-rep a1
657
658a2=(element)
659test-rep 'a2[0]'
660
661a3=(1 2 3)
662test-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
694declare -A ref=(['dummy']=v1)
695function 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
703v1=value
704test-op0 v1
705
706v2=
707test-op0 v2
708
709a1=()
710test-op0 a1
711
712a2=(element)
713test-op0 'a2[0]'
714
715a3=(1 2 3)
716test-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