Zeophlite
Zeophlite

Reputation: 1617

Bash nested quotes and eval

I'm having difficulty nested quotes within a bash script

argv="su -c '$RVM_PATH wrapper $config_rvm \'$PASSENGER_RVM_BIN $command $options\'' web"
eval $argv

The above got me

eval: line 162: unexpected EOF while looking for matching `''
eval: line 163: syntax error: unexpected end of file

Upvotes: 12

Views: 17078

Answers (4)

Tom Hale
Tom Hale

Reputation: 46983

Generic solution for eval with correctly quoted arguments

The following function uses the shell's own quoting mechanism to that I don't have to worry about how to correctly quote things:

function token_quote {
  local quoted=()
  for token; do
    quoted+=( "$(printf '%q' "$token")" )
  done
  printf '%s\n' "${quoted[*]}"
}

Example usage:

$ token_quote token 'single token' token
token single\ token token

Above, note the single token's space is quoted as \.

$ set $(token_quote token 'single token' token)
$ eval printf '%s\\n' "$@"
token
single token
token
$

This shows that the tokens are indeed kept separate.


Given some untrusted user input:

% input="Trying to hack you; date"

Construct a command to eval:

% cmd=(echo "User gave:" "$input")

Eval it, with seemingly correct quoting:

% eval "$(echo "${cmd[@]}")"
User gave: Trying to hack you
Thu Sep 27 20:41:31 +07 2018

Note you were hacked. date was executed rather than being printed literally.

Instead with token_quote():

% eval "$(token_quote "${cmd[@]}")"
User gave: Trying to hack you; date
%

eval isn't evil - it's just misunderstood :)

Upvotes: 0

Ignacio Vazquez-Abrams
Ignacio Vazquez-Abrams

Reputation: 799180

Use an array instead.

#!/bin/bash
cmd=(echo "foo bar")
"${cmd[@]}"

Upvotes: 8

Zeophlite
Zeophlite

Reputation: 1617

argv="su -c \"$RVM_PATH wrapper $config_rvm \\\"$PASSENGER_RVM_BIN $command $options\\\"\" web"

Upvotes: 13

ruakh
ruakh

Reputation: 183484

That's because \' doesn't have any special meaning within a single-quoted string; it means simply "backslash, followed by end-of-string".

One option is to use $'...' instead of '...'; that will let you use backslash-escapes. It would look like this:

argv="su -c $'$RVM_PATH wrapper $config_rvm \'$PASSENGER_RVM_BIN $command $options\'' web"

The downside is that if there's any chance that $RVM_PATH, $config_rvm, or any of the other variables could include a backslash, then it too could be interpreted as introducing a backslash-escape.

Upvotes: 9

Related Questions