【问题标题】:Pass command line arguments and command line argument传递命令行参数和命令行参数
【发布时间】:2016-03-25 20:10:08
【问题描述】:

我希望我的脚本被称为:

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

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

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

最好的方法是什么?

【问题讨论】:

    标签: bash bash4


    【解决方案1】:

    没有真正的好方法可以做到这一点,只有变通方法。几乎所有常见的解决方案都涉及将单个参数作为单个参数传递,因为另一种选择 - 将参数列表作为单个参数传递 - 每次你想要传递一个甚至稍微复杂的参数时都会让你跳过火圈(其中,它事实证明,将会很普遍;您的示例只是您即将陷入的元引用泥潭的开始。有关更多见解,请参阅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-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
    

    【讨论】:

      【解决方案2】:

      我找到了一种更短的方法,可以将脚本参数作为参数传递给命令,但只有限制:应该评估命令。

      根据您的问题,以下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
      

      【讨论】:

      • 这显然不能避免复杂的元引用;阅读我的回答中的 bash 常见问题解答。另外,您应该非常仔细地考虑eval grep "$2"eval grep $2 之间的区别;您可能指的是第一个,但两者都有极端情况。
      • 我同意。这不是一个常见的解决方案。这只是一个简单的快速解决方案,可以解决最常见的问题,例如传递带有空格作为参数的字符串。
      最近更新 更多