I have a bash
shell variable containing a string formed of multiple words delimited by whitespace. The string can contain escapes, such as escaped whitespace within a word. Words containing whitespace may alternatively be quoted.
A shell variable that is used unquoted ($FOO
instead of "$FOO"
) becomes multiple words but quotes and escapes in the original string have no effect.
How can a string be split into words, giving consideration to quoted and escaped characters?
Background
A server offers restricted access over ssh
using the ForceCommand
option in the sshd_config
file to force execution of a script regardless of the command-line given to the ssh
client.
The script uses the variable SSH_ORIGINAL_COMMAND
(which is a string, set by ssh
, that contains the command-line provided to the ssh
client) to set its argument list before proceeding. So, a user doing
$ ssh some_server foo 'bar car' baz
will see the script execute and it will have SSH_ORIGINAL_COMMAND
set to foo bar car baz
which would become four arguments when the script does
set -- ${SSH_ORIGINAL_COMMAND}
Not the desired result. So the user tries again:
$ ssh some_server foo bar\ car baz
Same result - the backslash in the second argument needs to be escaped for the client's shell so ssh
sees it. What about these:
$ ssh some_server foo 'bar\ car' baz
$ ssh some_server foo bar\\ car baz
Both work, as would a printf "%q"
quoting wrapper that can simplify the client-side quoting.
Client-side quoting allows ssh
to send the correctly quoted string to the server so that it receives SSH_ORIGINAL_COMMAND
with the backslash intact: foo bar\ car baz
.
However there is still a problem because set
does not consider the quoting or escaping. There is a solution:
eval set -- ${SSH_ORIGINAL_COMMAND}
but it is unacceptable. Consider
$ ssh some_server \; /bin/sh -i
Very undesirable: eval
can't be used because the input can't be controlled.
What is required is the string expansion capability of eval
without the execution part.
declare
instead ofeval
. But that's just as evil. And so islocal
.declare -a m="(${s})"
appears to expand quotes yet does not allow injecting a command by using a semicolon;
or a closing brace)
. It does throw a runtime syntax error that cannot be caught.