2

我想从 shell 脚本调用外部应用程序,但是这个 shell 脚本在单个变量中获取参数(来自其他脚本)。一切都很好,直到我不必对单个参数使用双引号,而是用空格分隔单词。

这是我的问题的简化示例(sh_param 仅打印所有传递的参数):

#!/bin/sh

pass() {
    echo "Result with \$@"
    ./sh_param $@
    echo "Result with \"\$@\""
    ./sh_param "$@"
    echo "Result with \$*"
    ./sh_param $*
    echo "Result with \"\$*\""
    ./sh_param "$*"
}

pass '"single param" separate params'

和结果(sh_param 只打印所有传递的参数):

Result with $@
Param: "single
Param: param"
Param: separate
Param: params
Result with "$@"
Param: "single param" separate params
Result with $*
Param: "single
Param: param"
Param: separate
Param: params
Result with "$*"
Param: "single param" separate params

而且我要:

Param: single param
Param: separate
Param: params
4

5 回答 5

2

脚本

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
于 2012-05-04T13:25:20.337 回答
1

如果您遇到单个变量,则必须使用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

请注意,双引号会被 shell 吃掉。

于 2012-05-04T15:48:18.043 回答
1

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!

于 2012-05-05T11:03:57.760 回答
1

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
于 2012-05-07T20:05:29.200 回答
0

sh_param这可能会奏效,尽管在不知道如何处理其论点的情况下很难判断。

#!/bin/sh

pass() {
    echo "Result with \"\$@\""
    ./sh_param "$@"
}

pass "single param" separate params
于 2012-05-04T13:25:18.150 回答