Reputation: 5216
Sample bash script
QRY="select * from mysql"
CMD="mysql -e \"$QRY\""
`$CMD`
I get errors because the * is getting evaluated as a glob (enumerating) files in my CWD.
I Have seen other posts that talk about quoting the "$CMD" reference for purposes of echo output, but in this case
"$CMD"
complains the whole literal string as a command.
If I
echo "$CMD"
And then copy/paste it to the command line, things seems to work.
Upvotes: 2
Views: 3398
Reputation: 437238
Note: anubhava's answer has the right solution.
This answer provides background information.
As for why your approach didn't work:
"$CMD"
doesn't work, because bash sees the entire value as a single token that it interprets as a command name, which obviously fails.
`$CMD`
i.e., enclosing $CMD
in backticks, is pointless in this case (and will have unintended side effects if the command produces stdout output[1]); using just:
$CMD
yields the same - broken - result (only more efficiently - by enclosing in backticks, you needlessly create a subshell; use backticks - or, better, $(...)
only when embedding one command in another - see command substitution).
$CMD
doesn't work,
*
subjects it to pathname expansion (globbing) - among other shell expansions.\
-escaping glob chars. in the string causes the \
to be preserved when the string is executed.While it may seem that you've enclosed the *
in double quotes by placing it (indirectly) between escaped double quotes (\"$QRY\"
) inside a double-quoted string, the shell does not see what's between these escaped double quotes as a single, double-quoted string.
Instead, these double quotes become literal parts of the tokens they abut, and the shell still performs word splitting (parsing into separate arguments by whitespace) on the string, and expansions such as globbing on the resulting tokens.
If we assume for a moment that globbing is turned off (via set -f
), here is the breakdown of the arguments passed to mysql
when the shell evaluates (unquoted) $CMD
:
-e # $1
- all remaining arguments are the unintentionally split SQL command."select # $2
- note that "
has become a literal part of the argument* # $3
from # $4
mysql" # $5
- note that "
has become a literal part of the argumentThe only way to get your solution to work with the existing, single string variable is to use eval
as follows:
eval "$CMD"
That way, the embedded escaped double-quoted string is properly parsed as a single, double-quoted string (to which no globbing is applied), which (after quote removal) is passed as a single argument to mysql
.
However, eval
is generally to be avoided due to its security implications (if you don't (fully) control the string's content, arbitrary commands could be executed).
Again, refer to anubhava's answer for the proper solution.
[1] A note re using `$CMD`
as a command by itself:
It causes bash to execute stdout output from $CMD
as another command, which is rarely the intent, and will typically result in a broken command or, worse, a command with unintended effects.
Try running `echo ha`
(with the backticks - same as: $(echo ha)
); you'll get -bash: ha: command not found
, because bash tries to execute the command's output - ha
- as a command, which fails.
Upvotes: 1
Reputation: 784998
You can just use:
qry='select * from db'
mysql -e "$qry"
This will not subject to *
expansion by shell.
If you want to store mysql
command line also then use BASH arrays:
cmd=(mysql -e "$qry")
"${cmd[@]}"
Upvotes: 4