https://susam.net/blog/shell-eval.html
Shell Eval
By Susam Pal on 06 Jan 2022
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, as per 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 for occur
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
Comments
---------------------------------------------------------------------
Home Blog Feed Subscribe About Maze GitHub Twitter
(c) 2006-2022 Susam Pal