0

I'm doing a script in bash where I need to declare arrays inside loops, so I made it like this:

variable=suffix
declare -a prefix_$variable='(item1 item2)'

then I should be able to use "prefix_$variable", but I can't, it fails.

But the declaration does work, because, after the declaration, I can then make echo ${prefix_sufix[@]} and it will echo the items, the one problem is that I must write the array name by myself.

Look at this for instance:

mint@ubuntu ~ $ variable=suffix
mint@ubuntu ~ $ declare -a prefix_$variable='(item1 item2)'
mint@ubuntu ~ $ echo ${prefix_suffix[@]}
item1 item2
mint@ubuntu ~ $ echo ${prefix_$variable[@]}
bash: ${prefix_$variable[@]}: bad substitution
mint@ubuntu ~ $ prefix_$variable[2]='none'
bash: prefix_suffix[2]=none: command not found

This makes very little sense, cause you can see it clearly is understanding the name of the variable, cause it says "prefix_suffix" in the error message, yet it doesn't execute it properly.

What's going on here and how can I solve this?

I already trying to rewrite that last part as "prefix_${variable}"[2]='none', prefix_${variable}[2]='none', $(prefix_$variable[2]='none'), nothing changes the result.

9
  • Do not post pictures. Edit the question and use the code blocks in the same way that you have done for everything else. Commented Apr 8, 2022 at 23:00
  • @NasirRiley ok, but this does mess with the actual color coding of the terminal, it's coloring it like it was code (including the messages) Commented Apr 8, 2022 at 23:06
  • It doesn't matter. Commented Apr 8, 2022 at 23:13
  • "it clearly is understanding the name of the variable, cause it says "prefix_suffix" in the error message" - no it's not, for exactly that reason. It's trying to find a command called prefix_suffix[2]=none. Not a variable assignment. Commented Apr 8, 2022 at 23:13
  • 1
    Alternatively, use a language that actually has good support for multi-level and/or structured variables (like multi-dimensional arrays, or arrays/hashes where each element is another array or hash...nested as deep as required by the data). perl, for example. or python. or C. or pretty much any language that isn't bash or sh. bash is great at orchestrating the execution of other programs (like awk or sed or perl or grep or whatever) but is terrible at doing data processing itself - that's what all those other tools/languages are for.
    – cas
    Commented Apr 9, 2022 at 0:59

2 Answers 2

1

A workaround is to use a nameref.

$ variable=suffix
$ declare -n shortname=prefix_$variable
$ declare -a prefix_$variable='(item1 item2)'
$ echo ${prefix_suffix[@]}
item1 item2
$ echo "${shortname[@]}"
item1 item2
$ shortname[2]='none'
$ echo ${prefix_suffix[@]}
item1 item2 none
1

"What's going on here" is shell operations and features don't just magically happen. All POSIX shells, including bash, are required to implement a definite sequence with only limited flexibilities.

abc_def=heffalump

is first parsed as a token which is an ASSIGNMENT_WORD and not contained in a compound command so it is processed as a simple command: the assignment is saved, expansions are done on the rest (which is empty so nothing happens), the assignment is expanded (which does nothing), and there is no command name (or arguments) so the value is assigned to the variable in the current environment aka the 'main' shell (as opposed to a subshell) and no attempt is made to execute a program.

abc_$xyz=heffalump

because abc_$xyz is not a valid variable name (must be only alphabetic, digits, and underscore, beginning with non-digit) may be parsed either as ASSIGNMENT_WORD or plain WORD; bash does the latter. As a result it is then processed by:

  1. The words that are recognized as variable assignments or redirections according to Shell Grammar Rules are saved for processing in steps 3 and 4.

No assignments or redirections were parsed so nothing is done.

  1. The words that are not variable assignments or redirections shall be expanded. If any fields remain following their expansion, the first field shall be considered the command name and remaining fields are the arguments for the command.

The word is expanded to (say) abc_eeyore=heffalump. There is only one word and it is considered the command name.

  1. Redirections shall be performed as described in Redirection.

There are no redirections so nothing is done.

  1. Each variable assignment shall be expanded for tilde expansion, parameter expansion, command substitution, arithmetic expansion, and quote removal prior to assigning the value.

There are no assignments so nothing is done.

If a simple command results in a command name and an optional list of arguments, [and] 1. if the command name does not contain any slash, [and it is not the name of a builtin, special utility, or function, it] shall be searched for using the PATH environment variable [and the program executed if found, otherwise it fails, which is your case].

In short, because of the order in which the shell is required to do things, your idea only works for certain builtin commands like declare where the variable reference is done by the individual command, not by the common shell parsing and preparation logic.

The traditional, and sledgehammer, solution to this (in any POSIX shell) is eval.

eval abc_$xyz=heffalump

is first parsed into two words and the second word expanded to abc_eeyore=heffalump, and then passed as the (single) argument to eval which starts over from the beginning, parsing it as a valid assignment (which it now is) and executing it.

However, because eval does everything over, it can also do extremely harmful things like expand abc_$xyz=heffalump to abc_; rm -fr all_valuable_data. (The classic example was the even more destructive rm -fr /, but that only worked if you were root, and with GNU it now doesn't work at all.) In general it isn't safe to use eval unless you know exactly what you're doing -- and if you did you wouldn't have asked this question.

A more specific and safe solution, but only in bash, is nameref

suffix=eeyore
declare -n ref=abc_$suffix
ref=(1 2 3)
echo ${ref[@]} ${abc_eeyore[@]}
-> 1 2 3 1 2 3

Also in bash for scalars (but not arrays as you want) there is indirection:

suffix=eeyore
ind=abc_$suffix
declare abc_$suffix=123 # or declare $ind=123
echo ${!ind}
-> 123

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .