【问题标题】:How can I use long options with the Bash getopts builtin?如何使用内置 Bash getopts 的长选项?
【发布时间】:2012-08-14 21:27:07
【问题描述】:

我正在尝试使用 Bash getopts 解析 -temp 选项。我这样调用我的脚本:

./myscript -temp /foo/bar/someFile

这是我用来解析选项的代码。

while getopts "temp:shots:o:" option; do
    case $option in
        temp) TMPDIR="$OPTARG" ;;
        shots) NUMSHOTS="$OPTARG" ;;
        o) OUTFILE="$OPTARG" ;;
        *) usage ;;
    esac
done
shift $(($OPTIND - 1))

[ $# -lt 1 ] && usage

【问题讨论】:

标签: bash command-line-arguments getopts


【解决方案1】:

您不能将 getopts Bash 内置函数用于长选项——至少,不必构建自己的解析函数。你应该看看 /usr/bin/getopt 二进制文件(在我的系统上由util-linux 包提供;你的里程可能会有所不同)。

请参阅 getopt(1) 了解特定的调用选项。

【讨论】:

  • 我认为/usr/bin/getopt 也不支持长选项。
【解决方案2】:

getopts 被 shell 程序用来解析只有 1 个字符的位置参数 (没有 GNU 风格的长选项(--myoption)或 XF86 风格的长选项(-myoption))

【讨论】:

    【解决方案3】:

    getopts 只能解析短选项。

    大多数系统也有一个外部的getopt 命令,但是 getopt 不是标准的,并且通常被设计破坏,因为它不能安全地处理所有参数(带有空格和空参数的参数),只有 GNU getopt 可以处理它们安全,但前提是您以特定于 GNU 的方式使用它。

    更简单的选择是两者都不使用,只需使用 while 循环迭代脚本的参数并自己进行解析。

    有关示例,请参阅 http://mywiki.wooledge.org/BashFAQ/035

    【讨论】:

    • 这里不需要重新发明轮子,只需使用 getopts 并查找其他方法来解析长参数,或者像 @mcoolive 在这里展示的那样欺骗它
    【解决方案4】:

    确实 builtin bash getopts 只解析短选项, 但是您仍然可以添加几行脚本来使 getopts 处理长选项。

    这是http://www.uxora.com/unix/shell-script/22-handle-long-options-with-getopts中的部分代码

      #== set options ==#
    SCRIPT_OPTS=':fbF:B:-:h'
    typeset -A ARRAY_OPTS
    ARRAY_OPTS=(
        [foo]=f
        [bar]=b
        [foobar]=F
        [barfoo]=B
        [help]=h
        [man]=h
    )
    
      #== parse options ==#
    while getopts ${SCRIPT_OPTS} OPTION ; do
        #== translate long options to short ==#
        if [[ "x$OPTION" == "x-" ]]; then
            LONG_OPTION=$OPTARG
            LONG_OPTARG=$(echo $LONG_OPTION | grep "=" | cut -d'=' -f2)
            LONG_OPTIND=-1
            [[ "x$LONG_OPTARG" = "x" ]] && LONG_OPTIND=$OPTIND || LONG_OPTION=$(echo $OPTARG | cut -d'=' -f1)
            [[ $LONG_OPTIND -ne -1 ]] && eval LONG_OPTARG="\$$LONG_OPTIND"
            OPTION=${ARRAY_OPTS[$LONG_OPTION]}
            [[ "x$OPTION" = "x" ]] &&  OPTION="?" OPTARG="-$LONG_OPTION"
    
            if [[ $( echo "${SCRIPT_OPTS}" | grep -c "${OPTION}:" ) -eq 1 ]]; then
                if [[ "x${LONG_OPTARG}" = "x" ]] || [[ "${LONG_OPTARG}" = -* ]]; then 
                    OPTION=":" OPTARG="-$LONG_OPTION"
                else
                    OPTARG="$LONG_OPTARG";
                    if [[ $LONG_OPTIND -ne -1 ]]; then
                        [[ $OPTIND -le $Optnum ]] && OPTIND=$(( $OPTIND+1 ))
                        shift $OPTIND
                        OPTIND=1
                    fi
                fi
            fi
        fi
    
        #== options follow by another option instead of argument ==#
        if [[ "x${OPTION}" != "x:" ]] && [[ "x${OPTION}" != "x?" ]] && [[ "${OPTARG}" = -* ]]; then 
            OPTARG="$OPTION" OPTION=":"
        fi
    
        #== manage options ==#
        case "$OPTION" in
            f  ) foo=1 bar=0                    ;;
            b  ) foo=0 bar=1                    ;;
            B  ) barfoo=${OPTARG}               ;;
            F  ) foobar=1 && foobar_name=${OPTARG} ;;
            h ) usagefull && exit 0 ;;
            : ) echo "${SCRIPT_NAME}: -$OPTARG: option requires an argument" >&2 && usage >&2 && exit 99 ;;
            ? ) echo "${SCRIPT_NAME}: -$OPTARG: unknown option" >&2 && usage >&2 && exit 99 ;;
        esac
    done
    shift $((${OPTIND} - 1))
    

    这是一个测试:

    # Short options test
    $ ./foobar_any_getopts.sh -bF "Hello world" -B 6 file1 file2
    foo=0 bar=1
    barfoo=6
    foobar=1 foobar_name=Hello world
    files=file1 file2
    
    # Long and short options test
    $ ./foobar_any_getopts.sh --bar -F Hello --barfoo 6 file1 file2
    foo=0 bar=1
    barfoo=6
    foobar=1 foobar_name=Hello
    files=file1 file2
    

    否则在最近的 Korn Shell ksh93 中,getopts 可以自然地解析长选项,甚至显示类似的手册页。 (见http://www.uxora.com/unix/shell-script/20-getopts-with-man-page-and-long-options

    米歇尔·冯维莱。

    【讨论】:

    • 很好,谢谢。不幸的是,如果参数包含 '=' 或参数是空字符串,它会中断。我应该建议一个支持 '=' 和空字符串作为参数的版本吗?
    【解决方案5】:

    正如其他人解释的那样,getopts 不会解析长选项。您可以使用 getopt,但它不可移植(并且在某些平台上已损坏...)

    作为一种解决方法,您可以实现一个 shell 循环。这是一个在使用标准 getopts 命令之前将长选项转换为短选项的示例(我认为它更简单):

    # Transform long options to short ones
    for arg in "$@"; do
      shift
      case "$arg" in
        "--help") set -- "$@" "-h" ;;
        "--rest") set -- "$@" "-r" ;;
        "--ws")   set -- "$@" "-w" ;;
        *)        set -- "$@" "$arg"
      esac
    done
    
    # Default behavior
    rest=false; ws=false
    
    # Parse short options
    OPTIND=1
    while getopts "hrw" opt
    do
      case "$opt" in
        "h") print_usage; exit 0 ;;
        "r") rest=true ;;
        "w") ws=true ;;
        "?") print_usage >&2; exit 1 ;;
      esac
    done
    shift $(expr $OPTIND - 1) # remove options from positional parameters
    

    【讨论】:

    • 这很好,只是如果传递了无效参数,它无法正确通知用户,而是声明illegal option -- -。我在脚本中添加了以下内容以提供帮助:"--"*) usage ${arg}; exit 2;;
    • 这可能是个好主意。我的示例可以以相同的方式进行改进,以允许使用“--separator=STRING”之类的语法。我选择实现一个简单的循环并且不允许所有可能的语法。它可以改进。
    • 这是迄今为止我找到的最直接的解决方案。这允许您继续使用 getopts 并支持-- 样式。不错!
    • 作为一个魅力。感谢您分享您的解决方案。
    • 请使用需要参数的长选项进行扩展。
    【解决方案6】:

    虽然这个问题是在 2 年前发布的,但我发现自己也需要支持 XFree86 风格的长选项;而且我还想从 getopts 中获取我能得到的东西。考虑 GCC 开关 -rdynamic。我将r 标记为标志字母,并期望dynamic$OPTARG 内...但是,我想拒绝-r dynamic,同时接受r 之后的其他选项。

    我在下面提出的想法建立在观察到$OPTIND 将比其他情况大一,如果空间(间隙)跟随标志。因此,我定义了一个 bash 变量来保存 $OPTIND 的先前值,称为 $PREVOPTIND,并在 while 循环结束时对其进行更新。如果$OPTIND1 大于$PREVOPTIND,我们没有差距(即-rdynamic);并且$GAP 设置为false。相反,如果 $OPTIND2 大于 $PREVOPTIND,我们确实有一个间隙(例如 -r dynamic),并且 $GAP 设置为 true

    usage() { echo usage: error from $1; exit -1; }
    
    OPTIND=1
    PREVOPTIND=$OPTIND
    while getopts "t:s:o:" option; do
      GAP=$((OPTIND-(PREVOPTIND+1)))
      case $option in
        t) case "${OPTARG}" in
             emp)                  # i.e. -temp
               ((GAP)) && usage "-${option} and ${OPTARG}"
               TMPDIR="$OPTARG"
               ;;
             *)
               true
               ;;
           esac
           ;;
        s) case "${OPTARG}" in
             hots)                 # i.e. -shots
               ((GAP)) && usage
               NUMSHOTS="$OPTARG"
               ;;
             *) usage "-${option} and ${OPTARG}" ;;
           esac
           ;;
        o) OUTFILE="$OPTARG" ;;
        *) usage "-${option} and ${OPTARG}" ;;
      esac
      PREVOPTIND=$OPTIND
    done
    shift $(($OPTIND - 1))
    

    【讨论】:

      【解决方案7】:

      感谢@mcoolive。

      我能够使用您的 $@ 想法将整个单词和长选项转换为单字母选项。想提醒任何使用这个想法的人,在通过 getopts 循环运行参数之前,我还必须包含 shift $(expr $OPTIND - 1)

      目的完全不同,但效果很好。

      # convert long word options to short word for ease of use and portability
      
      for argu in "$@"; do
        shift
        #echo "curr arg = $1"
        case "$argu" in
      "-start"|"--start")
                         # param=param because no arg is required
                         set -- "$@" "-s"
                         ;;
      "-pb"|"--pb"|"-personalbrokers"|"--personalbrokers")
                         # pb +arg required
                         set -- "$@" "-p $1"; #echo "arg=$argu"
                         ;;
      "-stop"|"--stop")
                         # param=param because no arg is required 
                         set -- "$@" "-S" 
                         ;;
                      #  the catch all option here removes all - symbols from an
                      #  argument. if an option is attempted to be passed that is
                      #  invalid, getopts knows what to do...
                     *)  [[ $(echo $argu | grep -E "^-") ]] && set -- "$@" "${argu//-/}" || echo "no - symbol. not touching $argu" &>/dev/null
                         ;;
      esac
      done
      
      #echo -e "\n final option conversions = $@\n"
      # remove options from positional parameters for getopts parsing
      shift $(expr $OPTIND - 1)
      
      declare -i runscript=0
      # only p requires an argument hence the p:
       while getopts "sSp:" param; do
      [[ "$param" == "p" ]] && [[ $(echo $OPTARG | grep -E "^-") ]] && funcUsage "order" 
      #echo $param
      #echo "OPTIND=$OPTIND"
      case $param in
      s)
             OPTARG=${OPTARG/ /}
             getoptsRan=1
             echo "$param was passed and this is it's arg $OPTARG"
             arg0=start
             ;;
       p)
             OPTARG=${OPTARG/ /}
             getoptsRan=1
             echo "$param was passed and this is it's arg $OPTARG"
             [[ "$OPTARG" == "all" ]] && echo -e "argument \"$OPTARG\" accepted. continuing." && (( runscript += 1 )) || usage="open"
             [[ $( echo $pbString | grep -w "$OPTARG" ) ]] && echo -e "pb $OPTARG was validated. continuing.\n" && (( runscript += 1 )) || usage="personal"
             [[ "$runscript" -lt "1" ]] && funcUsage "$usage" "$OPTARG"
             arg0=start
             ;;
      S)
             OPTARG=${OPTARG/ /}
             getoptsRan=1
             echo "$param was passed and this is it's arg $OPTARG"
             arg0=stop
             ;;
      *)
             getoptsRan=1
             funcUsage
             echo -e "Invalid argument\n"
             ;;
      esac
      done
      funcBuildExcludes "$@"
      shift $((OPTIND-1))
      

      【讨论】:

        【解决方案8】:

        如果你在使用内置getopts时遇到问题,一个简单的DIY:

        用途:

        $ ./test-args.sh --a1 a1 --a2 "a 2" --a3 --a4= --a5=a5 --a6="a 6"
        a1 = "a1"
        a2 = "a 2"
        a3 = "TRUE"
        a4 = ""
        a5 = "a5"
        a6 = "a 6"
        a7 = ""
        

        脚本:

        #!/bin/bash
        
        function main() {
            ARGS=`getArgs "$@"`
        
            a1=`echo "$ARGS" | getNamedArg a1`
            a2=`echo "$ARGS" | getNamedArg a2`
            a3=`echo "$ARGS" | getNamedArg a3`
            a4=`echo "$ARGS" | getNamedArg a4`
            a5=`echo "$ARGS" | getNamedArg a5`
            a6=`echo "$ARGS" | getNamedArg a6`
            a7=`echo "$ARGS" | getNamedArg a7`
        
            echo "a1 = \"$a1\""
            echo "a2 = \"$a2\""
            echo "a3 = \"$a3\""
            echo "a4 = \"$a4\""
            echo "a5 = \"$a5\""
            echo "a6 = \"$a6\""
            echo "a7 = \"$a7\""
        
            exit 0
        }
        
        
        function getArgs() {
            for arg in "$@"; do
                echo "$arg"
            done
        }
        
        
        function getNamedArg() {
            ARG_NAME=$1
        
            sed --regexp-extended --quiet --expression="
                s/^--$ARG_NAME=(.*)\$/\1/p  # Get arguments in format '--arg=value': [s]ubstitute '--arg=value' by 'value', and [p]rint
                /^--$ARG_NAME\$/ {          # Get arguments in format '--arg value' ou '--arg'
                    n                       # - [n]ext, because in this format, if value exists, it will be the next argument
                    /^--/! p                # - If next doesn't starts with '--', it is the value of the actual argument
                    /^--/ {                 # - If next do starts with '--', it is the next argument and the actual argument is a boolean one
                        # Then just repla[c]ed by TRUE
                        c TRUE
                    }
                }
            "
        }
        
        
        main "$@"
        

        【讨论】:

          【解决方案9】:

          实现这一点的最简单方法是借助 getopt--longoptions

          试试这个,希望有用

          # Read command line options
          ARGUMENT_LIST=(
              "input1"
              "input2"
              "input3"
          )
          
          
          
          # read arguments
          opts=$(getopt \
              --longoptions "$(printf "%s:," "${ARGUMENT_LIST[@]}")" \
              --name "$(basename "$0")" \
              --options "" \
              -- "$@"
          )
          
          
          echo $opts
          
          eval set --$opts
          
          while true; do
              case "$1" in
              --input1)  
                  shift
                  empId=$1
                  ;;
              --input2)  
                  shift
                  fromDate=$1
                  ;;
              --input3)  
                  shift
                  toDate=$1
                  ;;
                --)
                  shift
                  break
                  ;;
              esac
              shift
          done
          

          这就是你调用shell脚本的方式

          myscript.sh --input1 "ABC" --input2 "PQR" --input2 "XYZ"
          

          【讨论】:

            猜你喜欢
            • 2015-07-16
            • 2013-04-17
            • 1970-01-01
            • 2021-04-29
            • 2021-06-23
            • 2016-04-01
            • 2014-11-15
            • 2010-09-28
            相关资源
            最近更新 更多