Protecting arguments containing spaces from eval

Viewed 843

In order to get eval to work on commands that contain spaces inside one of the parameters, I have only found this to work so far:

eval 'sed 's/foo/foo'" "'bar/g' filename'

In a hypothetical program where users would enter a command and then the command and arguments to be fed to eval, this isn't a very elegant or robust solution. Are there any other ways to run the eval command so that the interface for my_command can be a little more user friendly? The following is an example of how the program accepts arguments now.

my_command 'sed 's/foo/foo'" "'bar/g' filename'

I would like the interface to work something like this:

my_command sed 's/foo/foo bar/g' filename

edit:

I'll try asking a different question:

How do I get bash to read input from the command line literally? I want the exact input to be preserved, so if there are quotes I want to keep them. I can accomplish what I want to do by using egrep to read from file and then sanitizing the input, like so:

egrep '/.*/' filename |
sed 's/\(.*\)['"'"']\(.*\) \(.*\)['"'"']\(.*\)/\1'"\'"'\2" "\3'"\'"'\4/g'

with "filename" containing this line

sed 's/foo/foo bar/g' file

this gives me the desired output of:

sed 's/foo/foo" "bar/g' file

Problem here is that I can't echo "$@" because bash interprets the quotes. I want the literal input without having to read from file.

4 Answers

Array quoting

The following keeps spaces in arguments by quoting each element of array:

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

Related