1

I am stuck trying to understand (reading the GNU Bash Reference Manual) why I can not assign a variable this way:

myvar=var
${myvar}=42

I would expect that after the second line, the var value is 42, but all I get is:

command not found: var=42

I will follow the manual (certainly with errors in my parenthesised comments...) to explain better why I am stuck:

  1. bash reads input from command line (in this case), and well ok let it read.
  2. bash breaks input into words and operators (no quoting here so I skip the quote rule...). These tokens are separated by metacharacters (=${} are not a metacharacters so there must be one token: the whole line)
  3. bash parses the token into simple and compoud commands (here I expect the '=' to be interpreted as assignment, ${myvar} to be the lvalue and 42 the rvalue).
  4. bash performs the various expansion.(${myvar} should be replaced by var)
  5. bash performs all necessary redirections (no redirection here).
  6. bash executes command (so well here bash should assign 42 to var)
Jens
  • 69,818
  • 15
  • 125
  • 179
David
  • 29
  • 5
  • https://stackoverflow.com/questions/9938649/indirect-variable-assignment-in-bash – Mat May 01 '20 at 11:39
  • Yes I red this question already but my problem is not that i really want to assign a variable this way. I 'just' can not understand why it does not works the way i describe. – David May 01 '20 at 11:56
  • Does this answer your question? [Bash variable referencing](https://stackoverflow.com/questions/39277932/bash-variable-referencing) – Keldorn May 01 '20 at 11:58
  • Not really, it is related of course, my question is about understanding why the command I type does not work. I don't really want to find a way to do it. – David May 01 '20 at 12:06
  • Note that you *could* make the assignment work with another round of shell processing. This is what `eval ${myvar}=42` does. But beware of the pitfalls of eval'ing arbitrary code. This is the stuff security vulnerabilities may be made of. – Jens May 01 '20 at 12:56
  • Yes, i already red few things about eval and try to keep me as far as possible from it... – David May 01 '20 at 13:32

2 Answers2

5

The = symbol is not a metacharacter.

Let's first start with the first mention of variable assignment: Section 3.4 Shell Parameters:

A parameter is an entity that stores values. It can be a name, a number [for positional parameters $0, $1, etc.], or one of the special characters listed below. A variable is a parameter denoted by a name. A variable has a value and zero or more attributes. [...]

A parameter is set if it has been assigned a value. The null string is a valid value. Once a variable is set, it may be unset only by using the unset builtin command.

A variable may be assigned to by a statement of the form

name=[value]

If value is not given, the variable is assigned the null string. All values undergo tilde expansion, parameter and variable expansion, command substitution, arithmetic expansion, and quote removal (detailed below).

(the text in square bracket is my addition).

And there are no mentions of name going through any expansions.

By the way, a name is defined as:

A word consisting solely of letters, numbers, and underscores, and beginning with a letter or underscore. Names are used as shell variable and function names. Also referred to as an identifier.

If we carry on reading a bit, we get to Section 3.7.1 Simple Command Expansion, where we read:

When a simple command is executed, the shell performs the following expansions, assignments, and redirections, from left to right.

  1. The words that the parser has marked as variable assignments (those preceding the command name) and redirections are saved for later processing.
  2. The words that are not variable assignments or redirections are expanded (see Shell Expansions). If any words remain after expansion, the first word is taken to be the name of the command and the remaining words are the arguments.
  3. Redirections are performed as described above (see Redirections).
  4. The text after the ‘=’ in each variable assignment undergoes tilde expansion, parameter expansion, command substitution, arithmetic expansion, and quote removal before being assigned to the variable.

And again, only the value undergoes expansions.


Now going through POSIX specs, Section 2.10.2 Shell Grammar Rules mentions:

  1. [Assignment preceding command name]

    a. [When the first word]

    If the TOKEN does not contain the character '=', rule 1 is applied. Otherwise, 7b shall be applied.

    b. [Not the first word]

    If the TOKEN contains an unquoted (as determined while applying rule 4 from Token Recognition) character that is not part of an embedded parameter expansion, command substitution, or arithmetic expansion construct (as determined while applying rule 5 from Token Recognition):

    • If the TOKEN begins with '=', then rule 1 shall be applied.

    • If all the characters in the TOKEN preceding the first such form a valid name (see XBD Name), the token ASSIGNMENT_WORD shall be returned.

    • Otherwise, it is unspecified whether rule 1 is applied or ASSIGNMENT_WORD is returned.

    c. Otherwise, rule 1 shall be applied.

Assignment to the name within a returned ASSIGNMENT_WORD token shall occur as specified in Simple Commands.

where a valid name is defined as:

In the shell command language, a word consisting solely of underscores, digits, and alphabetics from the portable character set. The first character of a name is not a digit.

gniourf_gniourf
  • 44,650
  • 9
  • 93
  • 104
  • Did not read the whole answer (yet) but I am not the one calling 'metacharacters', the GNU reference manual does here : https://www.gnu.org/software/bash/manual/bash.html#Definitions – David May 01 '20 at 12:38
  • @David, you're right, my first sentence is just plain wrong `:)` (I fixed it). – gniourf_gniourf May 01 '20 at 12:41
  • Now that i have red it entirely, it is really clear. Thank you. – David May 01 '20 at 12:43
2

Step 3 does not recognize ${myvar}=42 as an assignment, because the portion before the = is not a valid identifier; it's an as-of-yet-unexpanded parameter expansion. In the Definitions section of the man page, a name is defined as

A word consisting only of alphanumeric characters and under- scores, and beginning with an alphabetic character or an under- score. Also referred to as an identifier.

Note that ${myvar} is not a name, as it starts with a $.

Later, in the Parameters section, an assignment is defined with:

A variable may be assigned to by a statement of the form

    name=[value]

Thus, ${myvar}=42 is not an assignment.

As a result, the word is left as-is until step 4, when the expansion does happen, resulting in var=42.

bash isn't looking for assignments anymore, though, so by the time you get to step 6, the word in command position is var=42, which cannot be found as a command. Put simply, an assignment is not a kind of command.

By contrast, declare "$myvar=42" works because the assignment is a side effect of the command declare var=42, produced by step 4, when executed in step 6.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • Ok thank you, but then what is assignment ? Should I be able to find the answer in the manual ? – David May 01 '20 at 12:24
  • `Should I be able to find the answer in the manual?` Yes, `A variable may be assigned to by a statement of the form name=[value] ` from [bash reference manual shell parameters](https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Shell-Parameters). The _name_ part doesn't undergo any expansion and is defined in [definitions](https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Definitions). – KamilCuk May 01 '20 at 12:35