"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:
- 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.
- 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.
- Redirections shall be performed as described in Redirection.
There are no redirections so nothing is done.
- 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
prefix_suffix[2]=none
. Not a variable assignment.