Leo Jiang
Leo Jiang

Reputation: 26193

Why do quotes in shell scripts behave differently from quotes in shell commands?

I'm using WSL (Ubuntu 18.04) on Windows 10 and bash.

I have a file filename.gpg with the content:

export SOME_ENV_VAR='123'

Now I run the following commands:

$ $(gpg -d filename.gpg)
$ echo $SOME_ENV_VAR
'123' <-- with quotes

However, if I run it directly in the shell:

$ export SOME_ENV_VAR='123'
$ echo $SOME_ENV_VAR
123 < -- without quotes

Why does it behave like this? Why is there a difference between running a command using $() and running it directly?

Aside: I got it working using eval $(gpg -d filename), I have no idea why this works.

Upvotes: 1

Views: 262

Answers (1)

xhienne
xhienne

Reputation: 6144

Quotes in shell scripts do not behave differently from quotes in shell commands.

With the $(gpg -d filename.gpg) syntax, you are not executing a shell script, but a regular single command.

What your command does

  1. It executes gpg -d filename.gpg
  2. From the result, it takes the first (IFS-separated) word as the command to execute
  3. It takes every other (IFS-separated) words, including words from additional lines, as its parameters
  4. It executs the command

From the following practical examples, you can see how it differs from executing a shell script:

  1. Remove the word export from filename.gpg: the command is then SOME_ENV_VAR='123' which is not understood as a variable assignment (you will get SOME_ENV_VAR='123': command not found).
  2. If you add several lines, they won't be understood as separated command lines, but as parameters to the very first command (export).
  3. If you change export SOME_ENV_VAR='123' to export SOME_ENV_VAR=$PWD, SOME_ENV_VAR will not contain the content of variable PWD, but the string $var

Why is it so?

See how bash performs expansion when analyzing a command.

There are many steps. $(...) is called "command substitution" and is the fourth step. When it is done, none of the previous steps will be performed again. This explains why your command does not work when you remove the export word, and why variables are not substituted in the result.

Moreover "quote Removal" is the last step and the manual reads:

all unquoted occurrences of the characters ‘\’, ‘'’, and ‘"’ that did not result from one of the above expansions are removed

Since the single quotes resulted from the "command substitution" expansion, they were not removed. That's why the content of SOME_ENV_VAR is '123' and not 123.

Why does eval work?

Because eval triggers another complete parsing of its parameters. The whole set of expansions is run again.

From the manual:

The arguments are concatenated together into a single command, which is then read and executed

Note that this means that you are still running one single command, and not a shell script. If your filename.gpg script has several lines, subsequent lines will be added to the argument list of the first (and only) command.

What should I do then?

Just use source along with process substitution.

source <(gpg -d filename.gpg)

Contrary to eval, source is used to execute a shell script in the current context. Process substitution provides a pseudo-filename that contains the result of the substitution (i.e. the output of gpg).

Upvotes: 4

Related Questions