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
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!