Shell Eval
In this post, we will perform a few experiments to see the
usefulness of the eval command for a particular
scenario in a POSIX-compliant shell. At first, we prepare a test
file that contains a space in its name and define a variable as
follows:
$ echo lorem ipsum > "foo bar" $ cmd='cat "foo bar"'
We will use this file and the variable in the experiments below.
All output examples below are obtained using Dash 0.5.11 on a Debian
GNU/Linux 11.2 (bullseye) system. Dash stands for Debian Almquist
Shell which is a POSIX-compliant shell available in Debian. Any
POSIX conforming shell should produce similar output. On Zsh, use
the command emulate sh before running these examples to
get similar output.
Experiment 1
Now simply enter $cmd as a command into the shell. The
following error occurs:
$ $cmd cat: '"foo': No such file or directory cat: 'bar"': No such file or directory
The error occurs because the above command expands to the
command cat followed by two
arguments: "foo and bar". Such an
expansion occurs due to a concept known as field splitting. Quoting
from section 2.6.5 of
POSIX.1-2017:
After parameter expansion, command substitution, and arithmetic expansion, the shell shall scan the results of expansions and substitutions that did not occur in double-quotes for field splitting and multiple fields can result.
The shell shall treat each character of the IFS as a delimiter and use the delimiters as field terminators to split the results of parameter expansion, command substitution, and arithmetic expansion into fields.
By default, the space character belongs to IFS. Here
is an example command to verify this:
$ printf "$IFS" | od -tcx1
0000000 \t \n
20 09 0a
0000003
The hexadecimal code 20 in the output confirms that the
space character is present in the value of IFS.
Therefore, according to the POSIX specification, $cmd
first expands to cat "foo bar", then it is split into
three fields cat, "foo and
bar" and then the command cat is executed
with two arguments "foo and bar". Since
no files with those names exist, an error occurs.
Experiment 2
Next we try to double-quote the previous command to prevent field splitting and see what happens:
$ "$cmd" dash: 8: cat "foo bar": not found
The excerpt from the POSIX.1-2017 specification quoted in the
previous section shows that field splitting does not occur for
variable expansions within double quotes. So the entire
expansion cat "foo bar" remains intact as a single
field and is then executed as a command. Since there is no such
weirdly named command, we get the above error.
Experiment 3
Field splitting leads to an error as seen in the first experiment.
Preventing field splitting by double-quoting the variable expansion
also leads to an error as seen in the second experiment. How do we
execute the command in the cmd variable?
We need a way to somehow get the shell to parse cat "foo
bar" like a shell normally does, i.e. treat each unquoted
token as a separate field and each quoted token as a single one.
How do we get the shell to do that? Well, we can just invoke the
shell itself to parse our command:
$ sh -c "$cmd" lorem ipsum
But the above command invokes a new shell process. Can we avoid
that? Yes, using the eval command:
$ eval "$cmd" lorem ipsum