简体   繁体   中英

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 Directories${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

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. Eg, 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" => eg:

  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 :-)

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}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM