ChatGPT解决这个技术问题 Extra ChatGPT

How to execute a bash command stored as a string with quotes and asterisk [duplicate]

This question already has answers here: Why does shell ignore quoting characters in arguments passed to it through variables? [duplicate] (3 answers) Closed 6 years ago.

I try to execute the following command :

mysql AMORE -u username -ppassword -h localhost -e "SELECT  host  FROM amoreconfig"

I store it in a string :

cmd="mysql AMORE -u username -ppassword -h localhost -e\"SELECT  host  FROM amoreconfig\""

Test it :

echo $cmd
mysql AMORE -u username -ppassword -h localhost -e"SELECT host FROM amoreconfig"

Try to execute by doing :

$cmd

And I get the help page of mysql :

mysql  Ver 14.14 Distrib 5.1.31, for pc-linux-gnu (i686) using readline 5.1
Copyright 2000-2008 MySQL AB, 2008 Sun Microsystems, Inc.
This software comes with ABSOLUTELY NO WARRANTY. This is free software,
and you are welcome to modify and redistribute it under the GPL license
Usage: mysql [OPTIONS] [database]
(...)

I guess I am doing something plain wrong with the quotes but can't find out what is the problem.

I recommend that you read this: mywiki.wooledge.org/BashFAQ/050
@DennisWilliamson - top link; I especially like this: "If your head is SO far up your ass that you still think you need to write out every command you're about to run before you run it" - I wonder, how the author of that, would solve a script where you construct a command dynamically, and explicitly want to echo it - in order to prompt the user "Do you want to run this command?" before it's ran?...
@sdaau, depends on which of the approaches given in the FAQ is being used. For a function, one can print its text with declare -f; for an array (the typical "dynamically constructed" approach): printf '%q ' "${array[@]}"; echo.
The best-practices approach, by the way, is not to store your command as a string. If you want to dynamically construct it, do so with an array. Using eval, as the top answers here do, incurs substantial security risk (opening one up to shell injection attacks if any content is parameterized).
@DennisWilliamson -- i like doing if for a --dryrun feature in [big] shell that has multiple phases and the user might skip around. Make sense??

s
slebetman

Have you tried:

eval $cmd

For the follow-on question of how to escape * since it has special meaning when it's naked or in double quoted strings: use single quotes.

MYSQL='mysql AMORE -u username -ppassword -h localhost -e'
QUERY="SELECT "'*'" FROM amoreconfig" ;# <-- "double"'single'"double"
eval $MYSQL "'$QUERY'"

Bonus: It also reads nice: eval mysql query ;-)


Thanks, it works. How would I select all columns ? How can I escape '*' ?
See BashFAQ #48 for discussion of the security pitfalls around this use: mywiki.wooledge.org/BashFAQ/048
...the literal quotes in the eval statement, when they become syntactic via use of eval, can have their effect undone by any literal quotes within the data; thus, they don't provide effective security.
@joshmcode, ...if we want deployed systems running scripts built w/ advice from ServerFault to have injection vulnerabilities. Getting details right matters. The worst data-loss incident I've been present for was when someone didn't use adequate quoting when handling filenames that "couldn't ever" contain anything but hex digits. Until one day one did (due to a bug in a program building the files dumping random memory content into the buffer used as a name), and that script (responsible for pruning ancient backups) deleted months of billing data.
@joshmcode, (and yes, that matters here -- eval $cmd, unlike eval "$cmd", splits your input into words, evaluates each word as a glob, and then pastes them back together with spaces, so a command with a whitespace-surrounded asterisk in it could cause shell expansions in the current working directory's filenames to be evaluated).
C
Charles Duffy

Use an array, not a string, as given as guidance in BashFAQ #50.

Using a string is extremely bad security practice: Consider the case where password (or a where clause in the query, or any other component) is user-provided; you don't want to eval a password containing $(rm -rf .)!

Just Running A Local Command

cmd=( mysql AMORE -u username -ppassword -h localhost -e "SELECT  host  FROM amoreconfig" )
"${cmd[@]}"

Printing Your Command Unambiguously

cmd=( mysql AMORE -u username -ppassword -h localhost -e "SELECT  host  FROM amoreconfig" )
printf 'Proposing to run: '
printf '%q ' "${cmd[@]}"
printf '\n'

Running Your Command Over SSH (Method 1: Using Stdin)

cmd=( mysql AMORE -u username -ppassword -h localhost -e "SELECT  host  FROM amoreconfig" )
printf -v cmd_str '%q ' "${cmd[@]}"
ssh other_host 'bash -s' <<<"$cmd_str"

Running Your Command Over SSH (Method 2: Command Line)

cmd=( mysql AMORE -u username -ppassword -h localhost -e "SELECT  host  FROM amoreconfig" )
printf -v cmd_str '%q ' "${cmd[@]}"
ssh other_host "bash -c $cmd_str"

Consider the case where no passwords are in the query. Think how useful it would be.
@DavidBeckwith, why is the approach with security vulnerabilities more useful than the one without? What benefit does it add? You can still dynamically construct your arrays; you just get the benefit of not risking their contents being parsed in a manner different from that intended.
...that is to say: One can run cmd+=( -e "$query" ) to append those arguments to the existing array, and be assured that query will be added as a single argument to -e that's passed to mysql; no need to look into its contents to figure out if it spawns a subshell or escapes its quotes and launches a rootkit or whatever else.
It's more exciting if you have security vulnerabilities.
I... really don't know what I can say in response to that. Other than "please don't apply to work with me". Or, maybe, "please don't apply to work anywhere making products I use".
g
ghostdog74

try this

$ cmd='mysql AMORE -u root --password="password" -h localhost -e "select host from amoreconfig"'
$ eval $cmd

thanks, it works. I marked the other answer as the accepted one because it came before.
To the extent that it works, this works badly. Needs to be eval "$cmd", not eval $cmd, to handle cases where any word-split component could be glob-expanded to a file in the current directory -- or cases where characters in IFS can't be substituted for others harmlessly.
This solution risks a command injection security vulnerability if any of the input to the eval'ed string is user-supplied. The solution that @CharlesDuffy provides is much better.
D
David Beckwith

You don't need the "eval" even. Just put a dollar sign in front of the string:

cmd="ls"
$cmd

Works only for extremely simple commands; doesn't work for the one the OP gave in their example.
this worked for me for a nodejs tool instead of `$cmd`
@PauloOliveira, that's because what you were trying before was trying to capture the output of your command, break that output into words, and run those words as another command itself.
does not work in GitBash.
P
Paul Havens

To eliminate the need for the cmd variable, you can do this:

eval 'mysql AMORE -u root --password="password" -h localhost -e "select host from amoreconfig"'

If you're just going to have a hardcoded literal, why use eval at all, as opposed to just running mysql [...]?
eval with a constant literal makes no sense!