Bash – Why Can’t Variables Be Used as Prefix to a Command to Set Environment Variables?

bashenvironment-variablesvariable

Normally, it is possible to set an environment variable for a command by prefixing it like so:

hello=hi bash -c 'echo $hello'

I also know that we can use a variable to substitute any part of a command invocation like the following:

$ cmd=bash
$ $cmd -c "echo hi" # equivalent to bash -c "echo hi"

I was very surprised to find out that you cannot use a variable to prefix a command to set an environment variable. Test case:

$ prefix=hello=hi
$ echo $prefix # prints hello=hi
$ $prefix bash -c 'echo $hello'
hello=hi: command not found

Why can I not set the environment variable using a variable? Is the prefix part a special part? I was able to get it working by using eval in front, but I still do not understand why. I am using bash 4.4.

Best Answer

I suspect this is the part of the sequence that's catching you:

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

That's from the Bash reference manual in the section on Simple Command Expansion.

In the cmd=bash example, no environment variables are set, and bash processes the command line up through parameter expansion, leaving bash -c "echo hi".

In the prefix=hello=hi example, there are again no variable assignments in the first pass, so processing continues to parameter expansion, resulting in a first word of hello=hi.

Once the variable assignments have been processed, they are not re-processed during command execution.

See the processing and its results under set -x:

$ prefix=hello=hi
+ prefix=hello=hi
$ $prefix bash -c 'echo $hello'
+ hello=hi bash -c 'echo $hello'
-bash: hello=hi: command not found
$ hello=42 bash -c 'echo $hello'
+ hello=42
+ bash -c 'echo $hello'
42

For a safer variation of "variable expansion" -as- "environment variables" than eval, consider wjandrea's suggestion of env:

prefix=hello=hi
env "$prefix" bash -c 'echo "$hello"'
hi

It's not strictly a command-line variable assignment, since we're using the env utility's main function of assigning environment variables to a command, but it accomplishes the same goal. The $prefix variable is expanded during the processing of the command-line, providing the name=value to env, who passes it along to bash.

Related Question