Bash array parameter expansion within parameter name: bad substitution
I wrote a script in which I want the user to input a directory or many directories and each is checked for something unimportant to this discussion. All interior directories are also checked up to a specified depth.
While I can declare the array Directories0
(the input directories) to start, I cannot refer to it in any way... result: bad substitution. Obviously, Directories1
would be depth=1, Directories2
is depth=2, and so on...
Below is a snippet of code:
let Recurse=4 ## say... variable value ##
[ "$Recurse" ] && let MaxDepth="$Recurse" || let MaxDepth=0
declare -i depth=0
IFS=$'\n'
## declare -a Directories${depth}=("${@}") ## <—— doesn't work
## declare -a Directories${depth}="("${@}")" ## <—— works if the brackets only are quoted...
## declare -a Directories${depth}=\("${@}"\) ## <—— ... or escaped
declare -a "Directories${depth}=("${@}")"
IFS=$' \t\n'
## Nested loop, depth counter increases for each directory depth. I want to stop at a specific depth which is entered as an option ##
for (( depth = 0; depth <= MaxDepth; depth++ )); do ## MaxDepth is entered as option ##
until [ -z "${Directories${depth}[*]}" ]; do ## ***** bad substitution error ***** ##
declare input="$(follow "${Directories${depth}[0]}")" ## follow is a script that resolves symlinks and Finder aliases ##
CheckDirectory "${input%/}/" ## check directory ##
case $? in
## Tests passed ##
0) if [[ "$Recurse" && "$depth" -lt "$MaxDepth" ]]; then
IFS=$'\n'
## get ready to check sub-directories ##
declare -a Directories$(( depth + 1 ))="("${Directories$(( depth + 1 ))[@]}" $(find -P "${Directories${depth}[0]}" -type d -mindepth 1 -maxdepth 1 -exec follow '{}' \;))"
IFS=$' \t\n'
fi
true;;
## Tests failed ##
*) false;;
esac
[ $? -eq 0 ] && unset Director开发者_JS百科ies${depth}[0] || exit 1 ## if test fails, exit, if succeeds, move on to next directory ##
declare -a Directories${depth}="("${Directories${depth}[@]}")" ## re-shuffle the array to get rid of null value at index 0 ##
(( element++ ))
done
done
Below is a simplified version in case you don't want to go through the code above, this is the crux of the problem:
depth=2
declare -a "Directories${depth}=(yo man ma me mo)"
echo "${Directories${depth}[4]}"
> -bash: ${Directories${depth}[4]}: bad substitution
echo "${Directories2[4]}"
> mo
Solutions, anyone?
You need a literal variable name inside the ${}
construct. If you want to refer to a variable whose name is determined at runtime, you need to explicitly go through a level of indirection.
name="Directories${depth}[4]"
echo ${!name}
This doesn't help for assignments. As long as you're assigning in the current scope (and not in an encompassing scope), you can make computed assignments with the typeset
built-in. However, be careful: bash has a heuristic which makes it transform assignments that look like the array assignment syntax into array assignments. This means the code below is ok if the element is going to store an absolute file name (which always begins with a /
) but not if it is going to store an arbitrary file name (which could be something like (foo)
).
typeset "Directories${depth}[4]=new value"
It is, alternatively, possible to perform assignments to a variable whose name is determined at runtime with any shell by using eval
. This has the advantage of working in any shell, not just bash. You need to be very careful, though: it's hard to get the quoting right. It's best to make the argument of eval
do as little as possible. Use a temporary variable to obtain store the value.
eval "tmp=\${Directories${depth}[4]}"
echo "$tmp"
tmp="new value"
eval "Directories${depth}[4]=\$tmp"
Use eval, this works:
eval echo \${Directories${depth}[4]}
mo
try eval
#!/bin/bash depth=2 declare -a "Directories${depth}=(yo man ma me mo)" eval echo "\${Directories${depth}[4]}" echo "${Directories2[4]}"
You were almost there:
declare -a Directories${depth}="( yo man ma me mo )"
works (and without eval, btw). To access the value, use the ${!} syntax:
temp=Directories$depth[@]
echo ${!temp}
Perhaps this is a (belated) answer to what was being asked? (The question is not as clear as the asker believes, I feel....)
#!/bin/bash
for depth in {0..5}
do
var_value=(yo man ma me mo "last value")
var_name="directories${depth}"
eval "${var_name}=\"${var_value[${depth}]}\""
value="directories${depth}"
printf "directories{$depth} = ${!value}"
[ $depth -eq 0 ] && printf " \t directories0=$directories0\n"
[ $depth -eq 1 ] && printf " \t directories1=$directories1\n"
[ $depth -eq 2 ] && printf " \t directories2=$directories2\n"
[ $depth -eq 3 ] && printf " \t directories3=$directories3\n"
[ $depth -eq 4 ] && printf " \t directories4=$directories4\n"
[ $depth -eq 5 ] && printf " \t directories5=$directories5\n"
done
Which produces:
directories{0} = yo directories0=yo
directories{1} = man directories1=man
directories{2} = ma directories2=ma
directories{3} = me directories3=me
directories{4} = mo directories4=mo
directories{5} = last value directories5=last value
The main point being that if a variable name consists of another variable, how does one assign a value to it. E.g., if setting "foobar=value" is the normal way to set a variable to a value, then what if x=foo and y=bar, how does one set "${x}${y}=value" => e.g.:
foobar=test
echo $foobar
> test
x=foo
y=bar
eval "export ${x}${y}=\"value\""
echo $foobar
> value
If I've misunderstood the question, well, I'm not terribly surprised :-)
精彩评论