我希望我的脚本被称为:
./script -f "-a -b "foo bar"" baz
-a -b "foo bar"
传递给命令的命令行参数在哪里(例如grep
我的脚本内部执行的命令(例如)
这里的问题当然涉及引用:天真的方法(将命令行参数扩展到script
)对待foo
和bar
作为单独的参数,这当然是不可取的。
实现这一目标的最佳方法是什么?
没有真正的好方法可以做到这一点,只有变通方法。几乎所有常见的解决方案都涉及将单个参数作为单个参数传递,因为替代方案 - 将参数列表作为单个参数传递 - 每次你想要传递一个甚至稍微复杂的参数时都会让你跳过火圈(其中,它结果会很普遍;您的示例只是您即将陷入的元引用泥潭的开始。有关更多见解,请参阅此 Bash 常见问题解答条目)。
可能最常见的解决方案是将要传递的参数放在参数列表的末尾。这意味着您需要知道实际用于脚本的参数的结尾,这或多或少意味着存在固定数量的位置参数。通常,固定数字是 1。这种策略的一个例子是任何脚本解释器(bash
本身python
,等等),它只接受一个位置参数,即脚本的名称。这将使您的调用:
./script baz -a -b "foo bar"
这并不总是很方便。find
例如,该命令具有一个-exec
选项,该选项后跟以下参数中的实际命令。为此,您必须知道要通过的单词在哪里结束;find
通过使用特定的分隔符参数来解决这个问题:单个分号。(这是一个任意选择,但作为脚本参数很少见,因此通常可以解决。)在这种情况下,您的调用将如下所示:
./script -f -a -b "foo bar" \; baz
显然,;
需要引用,否则它会终止命令,但这不是一个复杂的引用问题。
这可以通过允许用户显式指定分隔符来扩展:
./script --arguments=--end -a -b "foo bar" --end baz
以下是find
-like 建议的一些示例代码:
# Use an array to accumulate the arguments to be passed through
declare -a passthrough=()
while getopts f:xyz opt; do
case "$opt" in
f)
passthrough+=("$OPTARG")
while [[ ${!OPTIND} != ';' ]]; do
if ((OPTIND > $#)); then
echo "Unterminated -f argument" >>/dev/stderr
exit 1
fi
passthrough+=("${!OPTIND}")
((++OPTIND))
done
((++OPTIND))
;;
# Handle other options
esac
done
# Handle positional arguments
# Use the passthrough arguments
grep "${passthrough[@]}" # Other grep arguments
我找到了一种更短的方法来将脚本参数作为参数传递给命令,但只有限制:应该评估一个命令。
根据您的问题,以下script
将做您想要的
#!/bin/bash
# Do whatever you want ...
# Just claim that 2nd quoted argument of script should be passed as
# a set of parameters of grep
eval grep $2
然后您可以script
使用包含空格的参数来调用它以传递给grep
./script -f "-a -b \"foo bar\"" baz
或者
./script -f '-a -b "foo bar"' baz
瞧!将grep
使用正确的参数调用
grep -a -b "foo bar"
例如,调用
./script -f '-i "system message" /etc/passwd' baz
然后得到正确的输出
dbus:x:81:81:System message bus:/:/sbin/nologin