1

I want to run a small bash script, but I'm getting extra junk added around quotes. I thought that using a single quote mark ' would give me a literal string?

I used set +x to see what the script is really doing. The code is this:

set -x
task='mysql --port=3336 --host=127.0.0.1 -u root training_service -e "DELETE FROM expert_bundle_claim; " '
echo $task
$task

When I run I'll see:

▶ bin/run-local.sh

+ task='mysql --port=3336 --host=127.0.0.1 -u root training_service -e "DELETE FROM expert_bundle_claim; " '
+ echo mysql --port=3336 --host=127.0.0.1 -u root training_service -e '"DELETE' FROM 'expert_bundle_claim;' '"'
mysql --port=3336 --host=127.0.0.1 -u root training_service -e "DELETE FROM expert_bundle_claim; "
+ mysql --port=3336 --host=127.0.0.1 -u root training_service -e '"DELETE' FROM 'expert_bundle_claim;' '"'
mysql  Ver 14.14 Distrib 5.6.47, for osx10.15 (x86_64) using  EditLine wrapper
Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.


and then errors.

So basically the actual SQL command "DELETE FROM expert_bundle_claim; " gets mangled to '"DELETE' FROM 'expert_bundle_claim;' '"'.

I tried it the with the ' and " inner/outer reversed, and it's even more mangled.

+ task='mysql --port=3336 --host=127.0.0.1 -u root training_service -e '\'' DELETE FROM expert_bundle_claim; '\'' '
+ mysql --port=3336 --host=127.0.0.1 -u root training_service -e ''\''' DELETE FROM 'expert_bundle_claim;' ''\'''

tried another way just piping into mysql with < filename.sql but that gets interpolated to a quoted '<'

+ task='mysql --port=3336 --host=127.0.0.1 -u root training_service < ./bin/revert_tasks.sql'
+ mysql --port=3336 --host=127.0.0.1 -u root training_service '<' ./bin/revert_tasks.sql

Any suggestions how best to handle this and prevent interpolation?

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
dcsan
  • 11,333
  • 15
  • 77
  • 118
  • thanks but it seems the answer is "never unless you really really have to". good answer below.. – dcsan Feb 07 '20 at 00:17
  • If you **really** need to do such a thing (though in the case you posted, the solution by John Kugelman makes more sense), the correct way to execute `$task` would be `eval $task`, but `eval` opens a new box of worms, so use it very carefully and only if you don't find a reasonable alternative. – user1934428 Feb 07 '20 at 07:54
  • are there benefits to using `eval $task` vs just `$task` (like, it might actually function :D) – dcsan Feb 09 '20 at 05:51
  • I don't know whether you call it a benefit or a disadvantage, but using `eval` causes the string to undergo parameter expansion. Compare `a='echo $PATH'`, and then `$a` vs. `eval 2"$a"`. Hence, it depends what effect you want to achieve in the end. – user1934428 Feb 09 '20 at 12:29

1 Answers1

3

Don't store code in variables. Getting the quotes right is a nightmare. Use functions instead. You can write the code exactly as you normally would without any special quoting or escaping.

task() {
    mysql --port=3336 --host=127.0.0.1 -u root training_service -e "DELETE FROM expert_bundle_claim; "
}

task

The reason your attempts don't work is due to Bash's expansion rules. When a variable like $task is expanded Bash doesn't parse the quotes that result from the expansion. It only looks at quotes that were in the original command-line. Quotes that result from expansion are treated as literal characters as if they were escaped with \" or '"'.

For more details see:

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
  • awesome thanks! works like a dream the solution is simple if you know it! However I do notice that the `set +x` is not showing the actual real stuff that's happening, it doesn't show anything after the `<` but the full command does seem to be running. – dcsan Feb 07 '20 at 00:16
  • 1
    @dcsan The output from `set -x` can be a bit confusing because it prints a command *that's equivalent to* what's being executed. In other words, it prints something that, if you typed it in on the command line, would do the same thing as what's happening. For instance (due to the oddities of parsing an expanded variable) a `<` in the variable gets treated as an argument rather than a redirect. Including a quoted (or escaped) less-than sign on a command line would have the same result, so `set -x` could render it as `'<'` or `"<"` or even `\<` -- it just happens to use the first one. – Gordon Davisson Feb 07 '20 at 08:38
  • but oddly there is *nothing* shown as an argument, the < isn't showing and the piped in filename also doesn't appear, just the base command... – dcsan Feb 09 '20 at 05:52