Pass shell-escaped string of arguments to a subcommand in Bourne shell

若如初见. 提交于 2019-12-10 15:37:54

问题


Say I have a command I want to run (cmd) and a variable containing the arguments I want to pass to the function (something like --foo 'bar baz' qux). Like so:

#!/bin/sh
command=cmd
args="--foo 'bar baz' qux"

The arguments contain quotes, like the ones shown above, that group together an argument containing a space. I'd then like to run the command:

$command $args

This, of course, results in running the command with four arguments: --foo, 'bar, baz', and qux. The alternative I'm used to (i.e., when using "$@") presents a different problem:

$command "$args"

This executes the command with one argument: --foo 'bar baz' qux.

How can I run the command with three arguments (--foo, bar baz, and qux) as intended?


回答1:


One possibility is to use eval:

#!/bin/sh

args="--foo 'bar baz' qux"
cmd="python -c 'import sys; print sys.argv'"

eval $cmd $args

That way you cause the command line to be interpreted rather than just split according to IFS. This gives the output:

$ ./args.sh 
['-c', '--foo', 'bar baz', 'qux']

So that you can see the args are passed as you wanted.




回答2:


Use an array to specify your argument list exactly, without string-splitting (which is what's doing the wrong thing here) getting in your way:

args=( --foo "bar baz" qux )
command "${args[@]}"

If you need to build your argument list dynamically, you can append to arrays with +=:

args=( )
while ...; do
   args+=( "$another_argument" )
done
call_your_subprocess "${args[@]}"

Note that the use of quotation marks, and [@] instead of [*], is essential.




回答3:


If you can throw away the current positional variables ($1...) you can use the following:

set -- '--foo' 'bar baz' 'qux'
echo "$#" # Prints "3" (without quotes)
echo "$2" # Prints "bar baz" (without quotes)
command "$@"

Just tested it in a #!/usr/bin/env sh script, so it works at least in Dash, and should work in any Bourne Shell variant. No eval, Python or Bash necessary.




回答4:


If you have the command in the form:

args="--foo 'bar baz' qux"

and getting the command as an array in the first place isn't an option, then you'll need to use eval to turn it back into an array:

$ args="--foo 'bar baz' qux"
$ eval "arr=($args)"

But it's important to note that this is unsafe if $args is being provided by an untrusted source, since it can be used to execute arbitrary commands, e.g. args='$(rm -rf /)'; eval "arr=($args)" will cause the above code to run the rm -rf / before you've even used arr.

Then you can use "${arr[@]}" to expand it as arguments to a command:

$ bash -c 'echo $0' "${arr[@]}"
--foo
$ bash -c 'echo $1' "${arr[@]}"
bar baz

or to run your command:

"$command" "${arr[@]}"

Note that there are differences between ${arr[*]}, ${arr[@]}, "${arr[*]}" and "${arr[@]}", and only the last of these does what you want in most cases




回答5:


It works for me by changing the IFS (the internal field seperator) of the shell (to only contain a newline). Here is how I override commands that use quoted arguments:

$ cat ~/.local/bin/make
#!/bin/sh

# THIS IS IMPORTANT! (don't split up quoted strings in arguments)
IFS="
"
exec /usr/bin/make ${@} -j6

(/bin/sh is dash)
It will eat the quotes when replacing the command with echo, so will look wrong when "testing"; but the command does get them as intended.

It can be tested by replacing the exec line with

for arg in $@; do echo $arg; done


来源:https://stackoverflow.com/questions/9982573/pass-shell-escaped-string-of-arguments-to-a-subcommand-in-bourne-shell

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!