I want to call external application from shell script, but this shell script gets parameters (from other script) in a single variable. All was OK until I did not have to use
If you're stuck with a single variable, you'll have to use eval
:
$ show() { i=0; for param; do ((i++)); echo "$i>$param"; done; }
$ show '"single param" separate params'
1>"single param" separate params
$ eval show '"single param" separate params'
1>single param
2>separate
3>params
Note that the double quotes are eaten by the shell.
Answering my own question. BIG thanks goes to pzanoni. xargs seems to parse correctly anything you are throwing to it :-) "$@", "$", $@ and $ works good with it. So my code now looks like:
#!/bin/sh
pass() {
echo $* | xargs ./sh_param
}
pass '"single param" separate params'
And result is what I wanted:
Param: single param
Param: separate
Param: params
This might work, although it's hard to tell without knowing how sh_param
handles its arguments.
#!/bin/sh
pass() {
echo "Result with \"\$@\""
./sh_param "$@"
}
pass "single param" separate params
pass() {
echo 'Result with "$@"'
sh_param "$@"
}
sh_param() {
for i in "$@"
do
echo Param: $i
done
}
pass "single param" separate param
Result with "$@"
Param: single param
Param: separate
Param: param
Avoid passing the whole thing as a single argument (as you do with the squotes). It's hard enough to make shell scripts that carefully preserve the number of arguments passed, without needing to make it harder by flattening them into strings and expanding them again.
If you do need to though, there are some best practices to follow. I had to write a script a while back that serves as a su/sudo wrapper: su takes a single argument that it passes to sh to evaluate; sudo takes any number of arguments that it passes on unmodified to an execv(e).
You have to be pretty careful about getting it portable and not running into problems in obscure shells. The whole script isn't very useful to post, but the general gist is to write some escaping functions and very perform the quoting to build up a bullet-proof, unreadably-cautiously escaped string that's safe to pass to eval
.
bash_escape() {
# backtick indirection strictly necessary here: we use it to strip the
# trailing newline from sed's output, which Solaris/BSD sed *always* output
# (unlike GNU sed, which outputs "test": printf %s test | sed -e s/dummy//)
out=`echo "$1" | sed -e s/\\'/\\''\\\\'\\'\\'/g`
printf \'%s\' "$out"
}
append_bash_escape() {
printf "%s " "$1"
bash_escape "$2"
}
sed_escape() {
out=`echo "$1" | sed -e 's/[\\/&]/\\\\&/g'`
printf %s "$out"
}
These useful functions let you do something like this to portably build command strings:
COMMAND=
while [ $# -gt 0 ] ; do
COMMAND=`append_bash_escape "$COMMAND" "$1"` ; shift
done
You can then manipulate the command string, for example by running bash_escape
on it and using sed_escape
to substitute into the string "su - root -c SUB_HERE"
, or just substitute it directly into a string like "sudo -- SUB_HERE"
. You then have something you can safely eval
without worrying about metacharacters, argument splitting, unescaped globs, and so on.
Be paranoid, and unittest your script with every nasty input you can think of to make sure your argument splitting and preserving really is correct!