When instructed to echo commands as they are executed ("execution trace"), both bash
and ksh
add single quotes around any word with meta-characters (*
, ?
, ;
, etc.) in it.
The meta-characters could have gotten into the word in a variety of ways. The word (or part of it) could have been quoted with single or double quotes, the characters could have been escaped with a \
, or they remained as the result of a failed filename matching attempt. In all cases, the execution trace will contain single-quoted words, for example:
$ set -x
$ echo foo\;bar
+ echo 'foo;bar'
This is just an artifact of the way the shells implement the execution trace; it doesn't alter the way the arguments are ultimately passed to the command. The quotes are added, printed, and discarded. Here is the relevant part of the bash
source code, print_cmd.c
:
/* A function to print the words of a simple command when set -x is on. */
void
xtrace_print_word_list (list, xtflags)
...
{
...
for (w = list; w; w = w->next)
{
t = w->word->word;
...
else if (sh_contains_shell_metas (t))
{
x = sh_single_quote (t);
fprintf (xtrace_fp, "%s%s", x, w->next ? " " : "");
free (x);
}
As to why the authors chose to do this, the code there doesn't say. But here's some similar code in variables.c
, and it comes with a comment:
/* Print the value cell of VAR, a shell variable. Do not print
the name, nor leading/trailing newline. If QUOTE is non-zero,
and the value contains shell metacharacters, quote the value
in such a way that it can be read back in. */
void
print_var_value (var, quote)
...
{
...
else if (quote && sh_contains_shell_metas (value_cell (var)))
{
t = sh_single_quote (value_cell (var));
printf ("%s", t);
free (t);
}
So possibly it's done so that it's easier to copy the command lines from the output of the execution trace and run them again.
Best Answer
This is the same process as in "run any command which will pass untrusted data to commands which interpret arguments as commands".
Your first command,
is processed as follows:
bash
runs with the arguments-c
,ls $1
,bash
,.; echo hello
. bash reads its arguments, notes the-c
option with the commandls $1
, and the extra argumentsbash
and.; echo hello
;when bash expands
ls $1
, it expands tols
with the arguments.;
,echo
,hello
and runs that.The semi-colon would have had to be processed before variable expansion to cause bash to run two different commands.
Your second command,
is processed as follows:
bash
runs with the arguments-c
,eval ls $1
,bash
,.; echo hello
. bash reads its arguments, notes the-c
option with the commandeval ls $1
etc.after expansion it runs
eval
with the argumentsls
,.;
,echo
,hello
;eval
then causes the arguments to be re-parsed, resulting in the execution ofls .
followed byecho hello
.