10

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.

  • 1
    You can use env instead of eval, which IIRC is more secure, but slower. – wjandrea Dec 20 '18 at 18:37
14

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.

  • 3
    Similarly, $foo=bar bash -c ... won't work, because $foo=bar isn't a variable assignment (whatever be the value of $foo), because $foo=bar doesn't fit the name=value pattern for variable assignment. – muru Dec 20 '18 at 3:45
3

Because $prefix isn't an assignment. @Jeff has the longer explanation.

You could do a similar thing with a function instead:

$ prefix() { hello=hi "$@"; }
$ prefix bash -c 'echo "$hello"'
hi

...and you can even stack those, if you like:

$ foo() { foo=123 "$@"; }
$ bar() { bar=456 "$@"; }
$ foo bar bash -c 'echo "$bar $foo"'
456 123

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.