传递命令行参数和命令行参数

Pass command line arguments and command line argument

我希望我的脚本像这样调用:

./script -f "-a -b "foo bar"" baz

其中 -a -b "foo bar" 是传递给由我的脚本在内部执行的命令(例如 grep)的命令行参数。

这里的问题当然与引用有关:天真的方法(将命令行参数扩展为 script)将 foobar 视为单独的参数,这是当然不受欢迎。

完成此任务的最佳方法是什么?

没有真正好的方法可以做到这一点,只有变通办法。几乎所有常见的解决方案都涉及将单个参数作为单个参数传递,因为替代方案——将参数列表作为单个参数传递——每次你想传递一个甚至稍微复杂的参数时都会让你跳过火圈(它,它结果会很普遍;您的示例只是您即将陷入元引用泥潭的开始。有关更多见解,请参阅 this Bash FAQ entry

可能最常见的解决方案是将要传递的参数放在参数列表的末尾。这意味着您需要知道实际用于脚本的参数的结尾,这或多或少意味着位置参数的数量是固定的。通常,固定数字是 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 的建议的示例代码:

# 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 

然后你可以用space调用这个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