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.
declareinstead 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.