348

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.

Stephane Rolland
  • 38,876
  • 35
  • 121
  • 169
Barth
  • 15,135
  • 20
  • 70
  • 105
  • 12
    I recommend that you read this: http://mywiki.wooledge.org/BashFAQ/050 – Dennis Williamson Jan 05 '10 at 11:43
  • 4
    @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 May 30 '13 at 20:26
  • @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`. – Charles Duffy Aug 28 '15 at 21:25
  • 6
    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). – Charles Duffy Aug 28 '15 at 21:30
  • @DennisWilliamson -- i like doing if for a --dryrun feature in [big] shell that has multiple phases and the user might skip around. Make sense?? – mobibob Oct 09 '18 at 20:06
  • @mobibob: using `getopts`, for example, if dryrun then [`set -x`](http://mywiki.wooledge.org/BashFAQ/050#I_want_a_log_of_my_script.27s_actions). – Dennis Williamson Oct 09 '18 at 20:44
  • my 2 cents: write a tmp file with the command to execute, chmod +x the file, then execute the file. – Thomas Decaux Jul 15 '22 at 13:20

5 Answers5

513

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 ;-)

slebetman
  • 109,858
  • 19
  • 140
  • 171
  • Thanks, it works. How would I select all columns ? How can I escape '*' ? – Barth Jan 05 '10 at 10:33
  • You do not need to escape asterisk (*) in this case. – filiprem Jan 05 '10 at 12:26
  • first attempt with both failed. I had to use eval $MYSQL \"$QUERY\" But then the first one returns +---+ | * | +---+ | * | (...) And the second with the escaping fails with : ERROR at line 1: Unknown command '\*'. Thanks for your help anyway :) – Barth Jan 05 '10 at 15:11
  • Fixed. Not sure exactly how it works, just followed a hunch. Maybe another shell guru can explain. – slebetman Jan 05 '10 at 15:28
  • 7
    See BashFAQ #48 for discussion of the security pitfalls around this use: http://mywiki.wooledge.org/BashFAQ/048 – Charles Duffy Sep 18 '15 at 19:16
  • 1
    ...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. – Charles Duffy Sep 18 '15 at 19:17
  • Also, the claim that `*` is interpreted in double quotes is simply false. `"*"` and `'*'` are precisely identical. – Charles Duffy Sep 07 '16 at 11:43
  • what about `eval $cmd $@`? – Charlie Parker Dec 15 '16 at 06:49
  • @CharlieParker, that's broken too. Unquoted, `$@` is exactly the same as `$*` -- you're expanding into a bunch of separate strings and passing them as individual arguments to `eval`, which concatenates them with whitespace and then tries to parse the result of this concatenation as if it were a shell command. The problem, of course, is that it's not guaranteed to *be* a valid shell command, and certainly not the command you'd have otherwise -- if `set -- "hello cruel" world` was run, then `$@` or `$*` will both have indistinguishable results from just `hello cruel world` without the quotes. – Charles Duffy Mar 14 '17 at 20:21
  • ...and the "put your query in literal single quotes" approach doesn't help at all if an attacker anticipates it. There's a reason my usual example of creating a filename that will perform a shell injection attack if substituted into code that's naively evaluated this way looks like `touch $'$(rm -rf $HOME)\'$(rm -rf $HOME)\''` -- you just put literal single quotes inside your generated content, and then they escape the single quotes this answer tries to inject. – Charles Duffy Mar 14 '17 at 20:24
  • This answer could be used all over ServerFault.. +1 – joshmcode Oct 13 '17 at 23:25
  • 5
    @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. – Charles Duffy Oct 19 '17 at 18:02
  • 1
    @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). – Charles Duffy Oct 19 '17 at 18:04
84

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"
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • 3
    Consider the case where no passwords are in the query. Think how useful it would be. – David Beckwith Aug 30 '15 at 06:55
  • 8
    @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. – Charles Duffy Aug 30 '15 at 15:24
  • 4
    ...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. – Charles Duffy Aug 30 '15 at 15:31
  • 15
    It's more exciting if you have security vulnerabilities. – David Beckwith Aug 31 '15 at 08:59
  • 8
    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". – Charles Duffy Mar 28 '17 at 16:09
  • 1
    that sure would be a spooky password. $(rm -rf .)! lordy. thankfully, Stack Overflow is still here!! – Tomachi Jun 08 '19 at 06:42
  • 1
    Using a string [with unsanitized user input] is extremely bad security practice. There's nothing wrong with using eval on a trusted string. It's not that hard to warn people about trusted vs untrusted input rather than shaking your finger about eval being evil – 111 Mar 08 '20 at 00:23
  • @glyph, treating a password as trusted input is evil, because people who are choosing passwords aren't thinking about making them shell-safe. (Mind you, I'm also staunchly against "sanitizing" input at all; if you're restricting your input domain to make it "safe" for a specific use case, you're contributing to problems like [Your name is invalid](https://blog.jgc.org/2010/06/your-last-name-contains-invalid.html), and that data will become *invalid* as soon as it's substituted into a language or context different from that it was sanitized for). – Charles Duffy Mar 08 '20 at 03:30
  • @glyph, ...so, the only Right Thing is to keep data and code out-of-band for each other, unless comprehensively escaped *for the specific language at hand* first. That escaping means, in the case of shell, `printf %q` -- which you'll notice this answer demonstrating -- or, in bash 5.0 or later, `${var@Q}`. – Charles Duffy Mar 08 '20 at 03:32
  • @glyph, ...similarly, folks who narrow their attempts to follow good practice to content that they explicitly consider untrusted are prone to trusting things they shouldn't. I had a former employer have a major data loss event due to a bad pointer in a 3rd-party library dumping garbage into a buffer that was later used to form a filename. "The only files in the directory are created by our own code following a set of rules we closely understand, so why *not* trust their names?" was the thinking; loss of billing backups followed. – Charles Duffy Mar 08 '20 at 03:34
  • @glyph, ...whereas follow best practices all the time, even when you think it doesn't matter, and you don't get surprised when it unexpectedly does. – Charles Duffy Mar 08 '20 at 03:35
  • @CharlesDuffy that password may come from a config file for all we know. excessive security where it doesn't matter is a waste of time. I'm not sure how to respond to being against sanitizing input. I guess don't accept any user input at all then, if you aren't willing to sanitize it. In 99% of cases, some input is going to come from users, and some of that input is going to be used in your application, and will have to be sanitized. – 111 Mar 08 '20 at 20:32
  • @CharlesDuffy escaping is just a form of sanitizing. By escaping characters you're sanitizing it. Semantics. – 111 Mar 08 '20 at 20:33
  • I ran into issues in bash4 and running commands that contain double-quotes. Had to pipe to bash in order to execute correctly. Example: `echo "${cmd[@]}" | bash` – Avraham May 07 '20 at 18:52
  • @glyph, ...first, concretely as opposed to philosophically, `echo "${cmd[@]}" | bash` is buggy. Try `cmd=( printf '%s\n' 'first line' 'second line' )` -- `"${cmd[@]}"` prints two lines; `echo "${cmd[@]}" | bash`... well, I'll let you see what it does. – Charles Duffy May 07 '20 at 20:07
  • @glyph, second, philosophically: Yes, "escaping is just a form of sanitizing", but it's objectively *worse* than keeping data and code out-of-band from each other. Look at the history behind `real_escape_string` in PHP -- it wouldn't have needed to exist if `escape_string` actually worked right. Whereas if you use bind variables and have a well-designed wire protocol, you don't need to worry about whether your escaping code is making data safe to mix into code -- it's keeping them completely separate in the first place! Same rule applies in shell; passing data out-of-band is harder to get... – Charles Duffy May 07 '20 at 20:11
  • ...wrong than trying to escape it safely and pass it in-band. (When you're passing data in-band whether the escaping is safe depends on the parser's state at the point of insertion -- what other quoting contexts exist, &c; pass it out-of-band and it's *always* safe). – Charles Duffy May 07 '20 at 20:12
  • @111 (formerly glyph): The other thing that makes "don't accept input without sanitizing it at acceptance time" bad policy is that the right kind of sanitation differs based on context. You need different escaping to make something safe for substitution into bash vs JSON vs xml vs yaml vs sql... and it's completely unreasonable to ask your _input_ code to anticipate where the data it's consuming will eventually be used. (My former corporate overlords had a policy of doing what you describe above, and their products were constantly being compromised; it's a particular sore point for me). – Charles Duffy Aug 08 '23 at 10:30
29

try this

$ cmd='mysql AMORE -u root --password="password" -h localhost -e "select host from amoreconfig"'
$ eval $cmd
ghostdog74
  • 327,991
  • 56
  • 259
  • 343
  • 1
    thanks, it works. I marked the other answer as the accepted one because it came before. – Barth Jan 05 '10 at 10:32
  • 4
    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. – Charles Duffy Feb 03 '16 at 18:34
  • 4
    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. – jsears Feb 25 '16 at 16:23
5

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

cmd="ls"
$cmd
David Beckwith
  • 2,679
  • 1
  • 17
  • 11
-5

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"'