8

Need help in fixing this bash script to set a variable with a value including double quotes. Somehow I am defining this incorrectly as my values foo and bar are not enclosed in double quotes as needed.

My script thus far:

#!/usr/local/bin/bash
set -e
set -x

host='127.0.0.1'
db='mydev'
_account="foo"
_profile="bar"
_version=$1
_mongo=$(which mongo);

exp="db.profile_versions_20170420.find({account:${_account}, profile:${_profile}, version:${_version}}).pretty();";
${_mongo} ${host}/${db} --eval "$exp"

set +x

Output shows:

+ host=127.0.0.1
+ db=mydev
+ _account=foo
+ _profile=bar
+ _version=201704112004
++ which mongo
+ _mongo=/usr/local/bin/mongo
+ exp='db.profile_versions_20170420.find({account:foo, profile:bar, version:201704112004}).pretty();'
+ /usr/local/bin/mongo 127.0.0.1/mydev --eval 'db.profile_versions_20170420.find({account:foo, profile:bar, version:201704112004}).pretty();'
MongoDB shell version: 3.2.4
connecting to: 127.0.0.1/mydev
2017-04-22T15:32:55.012-0700 E QUERY    [thread1] ReferenceError: foo is not defined :
@(shell eval):1:36

What i need is account:"foo", profile:"bar" to be enclosed in double quotes.

Cyrus
  • 84,225
  • 14
  • 89
  • 153
noober
  • 1,427
  • 3
  • 23
  • 36
  • 2
    `export` has a precise meaning in a shell script, and I see no evidence that any variables in this script are being exported. – William Pursell Apr 22 '17 at 22:47
  • 3
    @noober, btw, it's **much** more efficient to just run `mongo`, not to run `_mongo=$(which mongo)` and then invoke `"$_mongo"`. The latter spawns a subshell and then invokes an external process to do the lookup -- either of those steps is *individually* more expensive than just doing the PATH lookup internal to the shell itself. – Charles Duffy Apr 22 '17 at 22:50
  • @CharlesDuffy- duly noted. Will keep this more simple. – noober Apr 22 '17 at 23:01

4 Answers4

11

In bash (and other POSIX shells), the following 2 states are equivalent:

_account=foo
_account="foo"

What you want to do is to preserve the quotations, therefore you can do the following:

_account='"foo"'
Robert Seaman
  • 2,432
  • 15
  • 18
7

Since part of what you're doing here is forming JSON, consider using jq -- which will guarantee that it's well-formed, no matter what the values are.

host='127.0.0.1'
db='mydev'
_account="foo"
_profile="bar"
_version=$1

json=$(jq -n --arg account "$_account" --arg profile "$_profile" --arg version "$_version" \
  '{$account, $profile, version: $version | tonumber}')

exp="db.profile_versions_20170420.find($json).pretty();"
mongo "${host}/${db}" --eval "$exp"

This makes jq responsible for adding literal quotes where appropriate, and will avoid attempted injection attacks (for instance, via a version passed in $1 containing something like 1, "other_argument": "malicious_value"), by replacing any literal " in a string with \"; a literal newline with \n, etc -- or, with the | tonumber conversion, failing outright with any non-numeric value.


Note that some of the syntax above requires jq 1.5 -- if you have 1.4 or prior, you'll want to write {account: $account, profile: $profile} instead of being able to write {$account, $profile} with the key names inferred from the variable names.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
5

When you need to use double quotes inside a double quoted string, escape them with backslashes:

$ foo="acount:\"foo\"" sh -c 'echo $foo'
  acount:"foo"
Petr Skocik
  • 58,047
  • 6
  • 95
  • 142
4

I needed to enquote something already in a variable and stick that in a variable. Expanding on Robert Seaman's answer, I found this worked:

VAR='"'$1'"'

(single quote, double quote, single quote,
variable,
single quote, double quote, single quote)

Dharman
  • 30,962
  • 25
  • 85
  • 135