Bash – Running a constructed command from bash script

bashenvironment-variablesvariable substitution

I constructed a command like this in a bash script

a="my_cmd";
a="$a --verbose";
echo $a;
$a;

It works and executes my command correctly. But when I add an environment variable to the mix it breaks.

a="my_cmd";
a="URL=myurl $a --verbose";
echo $a;
$a;

It breaks at "URL=myurl" saying No such file or directory, but if I run it using eval() the command works correctly.

a="my_cmd";
a="URL=myurl $a --verbose";
echo $a;
eval "$a";

Why adding environment variable to the mix broke my command in second example.

Best Answer

When you leave a variable expansion unquoted, it undergoes word splitting and filename expansion (i.e. globbing). It isn't parsed as a shell command. In general, when you dynamically construct a shell snippet to execute, the right way to execute it is eval "$a" where a contains the string to parse as shell code.

In your first snippet, the value of a is the string my_cmd --verbose. Word splitting breaks it into two words my_cmd and --verbose. Globbing does nothing since there are no wildcards in any of the words. The command resulting from the expansion of $a thus consists of two words my_cmd and --verbose, so the command my_cmd (which could be an alias, a function, a builtin or an executable in the PATH) is executed with the single argument --verbose.

In the second snippet, things are similar, with three words resulting from the expansion: URL=myurl, my_cmd and --verbose. This results in an attempt to execute the command URL=myurl with two arguments.

The shell command URL=myurl my_cmd --verbose is parsed differently: the first word is parsed as an assignment to the variable URL, and since there is a command name after it the assignment sets the environment variable for the duration of the command only. This is part of parsing, not something that's done after expansion, so it requires the equal sign to be part of the shell source code, the equal sign can't be the result of some expansion.

Don't store a command with parameters into a string. Use an array. For a complex command, such as one where you set variables or perform redirection in addition to running the command, use a function if at all possible, and if not use eval (taking great care of proper quoting).

Related Question