【问题标题】:How do I parse command line arguments in Bash?如何在 Bash 中解析命令行参数?
【发布时间】:2010-09-16 13:47:58
【问题描述】:

说,我有一个脚本可以用这一行调用:

./myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile

或者这个:

./myscript -v -f -d -o /fizz/someOtherFile ./foo/bar/someFile 

在每种情况下(或两者的某种组合)$v$f$d 都将被设置为 true$outFile 的可接受的解析方式是什么?到/fizz/someOtherFile?

【问题讨论】:

  • 对于 zsh 用户,有一个很棒的内置函数 zparseopts 可以做到:zparseopts -D -E -M -- d=debug -debug=d 并且在 $debug 数组中同时拥有 -d--debug echo $+debug[1] 将返回 0 或 1 如果使用其中之一。参考:zsh.org/mla/users/2011/msg00350.html
  • 非常好的教程:linuxcommand.org/lc3_wss0120.php。我特别喜欢“命令行选项”示例。
  • 我为你创建了一个脚本,它被称为 - github.com/unfor19/bargs
  • 另请参阅Giving a bash script the option to accepts flags, like a command?,了解详细的、临时的、长短选项解析器。它不会尝试处理附加到短选项的选项参数,也不会尝试处理带有= 将选项名称与选项值分开的长选项(在这两种情况下,它只是假设选项值在下一个参数中)。它也不处理短选项聚类——这个问题不需要它。
  • This great tutorial by Baeldung 展示了在 bash 中处理命令行参数的 4 种方法,包括:1) 位置参数 $1$2 等,2) 带有 getopts 和 @987654343 的标志@, 3) 循环所有参数 ($@),和 4) 使用 $#$1shift 运算符循环所有参数。

标签: bash command-line scripting arguments getopts


【解决方案1】:

这是一个 getopts,它用最少的代码实现解析,并允许您在一种情况下使用带有子字符串的 eval 来定义您希望提取的内容。

基本上eval "local key='val'"

function myrsync() {

        local backup=("${@}") args=(); while [[ $# -gt 0 ]]; do k="$1";
                case "$k" in
                    ---sourceuser|---sourceurl|---targetuser|---targeturl|---file|---exclude|---include)
                        eval "local ${k:3}='${2}'"; shift; shift    # Past two arguments
                    ;;
                    *)  # Unknown option  
                        args+=("$1"); shift;                        # Past argument only
                    ;;                                              
                esac                                                
        done; set -- "${backup[@]}"                                 # Restore $@


        echo "${sourceurl}"
}

将变量声明为局部变量而不是全局变量作为此处的大多数答案。

称为:

myrsync ---sourceurl http://abc.def.g ---sourceuser myuser ... 

${k:3} 基本上是从键中删除第一个 --- 的子字符串。

【讨论】:

    【解决方案2】:

    有几种方法可以解析 cmdline args(例如 GNU getopt(不可移植)vs BSD (MacOS) getopt vs getopts) - 都有问题。这个解决方案

    • 便携!
    • 零依赖,仅依赖 bash 内置
    • 允许短期和长期选项
    • 处理空格或同时在选项和参数之间使用= 分隔符
    • 支持串联短选项样式-vxf
    • 处理带有可选参数的选项(例如--color vs --color=always),
    • 正确检测并报告未知选项
    • 支持-- 表示选项结束,并且
    • 与相同功能集的替代方案相比,不需要代码膨胀。 IE。简洁,因此更易于维护

    例子:任何一个

    # flag
    -f
    --foo
    
    # option with required argument
    -b"Hello World"
    -b "Hello World"
    --bar "Hello World"
    --bar="Hello World"
    
    # option with optional argument
    --baz
    --baz="Optional Hello"
    

    #!/usr/bin/env bash
    
    usage() {
      cat - >&2 <<EOF
    NAME
        program-name.sh - Brief description
     
    SYNOPSIS
        program-name.sh [-h|--help]
        program-name.sh [-f|--foo]
                        [-b|--bar <arg>]
                        [--baz[=<arg>]]
                        [--]
                        FILE ...
    
    REQUIRED ARGUMENTS
      FILE ...
              input files
    
    OPTIONS
      -h, --help
              Prints this and exits
    
      -f, --foo
              A flag option
          
      -b, --bar <arg>
              Option requiring an argument <arg>
    
      --baz[=<arg>]
              Option that has an optional argument <arg>. If <arg>
              is not specified, defaults to 'DEFAULT'
      --     
              Specify end of options; useful if the first non option
              argument starts with a hyphen
    
    EOF
    }
    
    fatal() {
        for i; do
            echo -e "${i}" >&2
        done
        exit 1
    }
    
    # For long option processing
    next_arg() {
        if [[ $OPTARG == *=* ]]; then
            # for cases like '--opt=arg'
            OPTARG="${OPTARG#*=}"
        else
            # for cases like '--opt arg'
            OPTARG="${args[$OPTIND]}"
            OPTIND=$((OPTIND + 1))
        fi
    }
    
    # ':' means preceding option character expects one argument, except
    # first ':' which make getopts run in silent mode. We handle errors with
    # wildcard case catch. Long options are considered as the '-' character
    optspec=":hfb:-:"
    args=("" "$@")  # dummy first element so $1 and $args[1] are aligned
    while getopts "$optspec" optchar; do
        case "$optchar" in
            h) usage; exit 0 ;;
            f) foo=1 ;;
            b) bar="$OPTARG" ;;
            -) # long option processing
                case "$OPTARG" in
                    help)
                        usage; exit 0 ;;
                    foo)
                        foo=1 ;;
                    bar|bar=*) next_arg
                        bar="$OPTARG" ;;
                    baz)
                        baz=DEFAULT ;;
                    baz=*) next_arg
                        baz="$OPTARG" ;;
                    -) break ;;
                    *) fatal "Unknown option '--${OPTARG}'" "see '${0} --help' for usage" ;;
                esac
                ;;
            *) fatal "Unknown option: '-${OPTARG}'" "See '${0} --help' for usage" ;;
        esac
    done
    
    shift $((OPTIND-1))
    
    if [ "$#" -eq 0 ]; then
        fatal "Expected at least one required argument FILE" \
        "See '${0} --help' for usage"
    fi
    
    echo "foo=$foo, bar=$bar, baz=$baz, files=${@}"
    

    【讨论】:

      【解决方案3】:

      我想分享我为解析选项所做的工作。 这里的答案没有满足我的一些需求,所以我不得不想出这个:https://github.com/MihirLuthra/bash_option_parser

      这支持:

      • 子选项解析
      • 选项的别名
      • 可选参数
      • 可变参数
      • 打印使用和错误

      假设我们有一个名为fruit 的命令,用法如下:

      fruit <fruit-name> ...
         [-e|—-eat|—-chew]
         [-c|--cut <how> <why>]
         <command> [<args>] 
      

      -e 不带参数
      -c 带两个参数,即如何剪切以及为什么要剪切
      fruit 本身至少需要一个参数。
      &lt;command&gt; 用于像@ 这样的子选项987654330@、orange 等(类似于git 有子选项commitpush 等)

      所以要解析它:

      parse_options \
          'fruit'                         '1 ...'  \
          '-e'     , '--eat' , '--chew'   '0'      \
          '-c'     , '--cut'              '1 1'    \
          'apple'                         'S'      \
          'orange'                        'S'      \
          ';' \
          "$@"
      

      现在如果有任何使用错误,可以使用option_parser_error_msg打印如下:

      retval=$?
      
      if [ $retval -ne 0 ]; then
          # this will manage error messages if
          # insufficient or extra args are supplied
      
          option_parser_error_msg "$retval"
      
          # This will print the usage
          print_usage 'fruit'
          exit 1
      fi
      

      现在检查是否通过了某些选项,

      if [ -n "${OPTIONS[-c]}" ]
      then
          echo "-c was passed"
      
          # args can be accessed in a 2D-array-like format
          echo "Arg1 to -c = ${ARGS[-c,0]}"
          echo "Arg2 to -c = ${ARGS[-c,1]}"
      
      fi
      

      子选项解析也可以通过将$shift_count 传递给parse_options_detailed 来完成,这使得它在将args 移动到子选项的args 后开始解析。这在example 中进行了演示。

      自述文件和示例中提供了详细说明 在repository

      【讨论】:

        【解决方案4】:

        我受到相对简单的answer by @bronson 的启发,并试图尝试改进它(不增加太多复杂性)。结果如下:

        另一个 Shell 参数解析器 (ASAP) – POSIX,没有 getopt*

        • 使用任何-n [arg]-abn [arg]--name [arg] --name=arg 样式的选项;
        • 参数可以按任何顺序出现,只有位置参数留在循环后$@
        • 使用 -- 强制将剩余的参数视为位置参数;
        • 检测无效选项和缺失参数;
        • 不依赖于getopt(s) 或外部工具(一个功能使用简单的sed 命令);
        • 便携、紧凑、可读性强,具有独立功能
        # Convenience functions.
        usage_error () { echo >&2 "$(basename $0):  $1"; exit 2; }
        assert_argument () { test "$1" != "$EOL" || usage_error "$2 requires an argument"; }
        
        # One loop, nothing more.
        EOL=$(echo '\01\03\03\07')
        if [ "$#" != 0 ]; then
          set -- "$@" "$EOL"
          while [ "$1" != "$EOL" ]; do
            opt="$1"; shift
            case "$opt" in
        
              # Your options go here.
              -f|--flag) flag=true;;
              -n|--name) assert_argument "$1" $opt; name="$1"; shift;;
        
              -|''|[^-]*) set -- "$@" "$opt";;                                          # positional argument, rotate to the end
              # Extra features (you may remove any line you don't need):
              --*=*)      set -- "${opt%%=*}" "${opt#*=}" "$@";;                        # convert '--name=arg' to '--name' 'arg'
              -[^-]?*)    set -- $(echo "${opt#-}" | sed 's/\(.\)/ -\1/g') "$@";;       # convert '-abc' to '-a' '-b' '-c'
              --)         while [ "$1" != "$EOL" ]; do set -- "$@" "$1"; shift; done;;  # process remaining arguments as positional
              -*)         usage_error "unknown option: '$opt'";;                        # catch misspelled options
              *)          usage_error "this should NEVER happen ($opt)";;               # sanity test for previous patterns
            esac
          done
          shift # $EOL
        fi
        
        # Do something cool with "$@"... \o/
        

        注意: 我知道... 二进制模式 0x01030307 的参数可能会破坏逻辑。但是,如果有人在命令行中传递这样的参数,他们应该得到它。

        【讨论】:

        • 好聪明的方法。我从现在开始使用它,直到找到更好的方法或发现错误 ;-)
        • 非常适合我以任何顺序混合位置和可选参数的用例,谢谢。
        【解决方案5】:

        我写了一个脚本,可以帮助轻松解析命令行参数 - https://github.com/unfor19/bargs

        示例

        $ bash example.sh -n Willy --gender male -a 99
        Name:      Willy
        Age:       99
        Gender:    male
        Location:  chocolate-factory
        
        $ bash example.sh -n Meir --gender male
        [ERROR] Required argument: age
        
        Usage: bash example.sh -n Willy --gender male -a 99
        
        --person_name  |  -n  [Willy]              What is your name?
        --age          |  -a  [Required]
        --gender       |  -g  [Required]
        --location     |  -l  [chocolate-factory]  insert your location
        
        $ bash example.sh -h
        
        Usage: bash example.sh -n Willy --gender male -a 99
        --person_name  |  -n  [Willy]              What is your name?
        --age          |  -a  [Required]
        --gender       |  -g  [Required]
        --location     |  -l  [chocolate-factory]  insert your location
        

        【讨论】:

          【解决方案6】:

          又一个选项解析器(生成器)

          一个优雅的 shell 脚本选项解析器(完全支持所有 POSIX shell) https://github.com/ko1nksm/getoptions(更新:2021-05-02发布v3.3.0)

          getoptions 是一个新的选项解析器(生成器),用 POSIX 兼容的 shell 脚本编写,并于 2020 年 8 月发布。它适用于那些希望在你的 shell 中支持 POSIX / GNU 样式选项语法的人脚本。

          支持的语法有-a+a-abc-vvv-p VALUE-pVALUE--flag--no-flag--with-flag、@98765433@1、@98765433@1 、--param=VALUE--option[=VALUE]--no-option--

          它支持子命令、验证、缩写选项和自动帮助生成。并且适用于所有 POSIX shell(dash 0.5.4+、bash 2.03+、ksh88+、mksh R28+、zsh 3.1.9+、yash 2.29+、busybox ash 1.1.3+ 等)。

          #!/bin/sh
          
          VERSION="0.1"
          
          parser_definition() {
            setup   REST help:usage -- "Usage: example.sh [options]... [arguments]..." ''
            msg -- 'Options:'
            flag    FLAG    -f --flag                 -- "takes no arguments"
            param   PARAM   -p --param                -- "takes one argument"
            option  OPTION  -o --option on:"default"  -- "takes one optional argument"
            disp    :usage  -h --help
            disp    VERSION    --version
          }
          
          eval "$(getoptions parser_definition) exit 1"
          
          echo "FLAG: $FLAG, PARAM: $PARAM, OPTION: $OPTION"
          printf '%s\n' "$@" # rest arguments
          

          它解析以下参数:

          example.sh -f --flag -p VALUE --param VALUE -o --option -oVALUE --option=VALUE 1 2 3
          

          并自动生成帮助。

          $ example.sh --help
          
          Usage: example.sh [options]... [arguments]...
          
          Options:
            -f, --flag                  takes no arguments
            -p, --param PARAM           takes one argument
            -o, --option[=OPTION]       takes one optional argument
            -h, --help
                --version
          

          它也是一个选项解析器生成器,生成以下简单的选项解析代码。如果您使用生成的代码,则不需要getoptions实现真正的可移植性和零依赖。

          FLAG=''
          PARAM=''
          OPTION=''
          REST=''
          getoptions_parse() {
            OPTIND=$(($#+1))
            while OPTARG= && [ $# -gt 0 ]; do
              case $1 in
                --?*=*) OPTARG=$1; shift
                  eval 'set -- "${OPTARG%%\=*}" "${OPTARG#*\=}"' ${1+'"$@"'}
                  ;;
                --no-*|--without-*) unset OPTARG ;;
                -[po]?*) OPTARG=$1; shift
                  eval 'set -- "${OPTARG%"${OPTARG#??}"}" "${OPTARG#??}"' ${1+'"$@"'}
                  ;;
                -[fh]?*) OPTARG=$1; shift
                  eval 'set -- "${OPTARG%"${OPTARG#??}"}" -"${OPTARG#??}"' ${1+'"$@"'}
                  OPTARG= ;;
              esac
              case $1 in
                '-f'|'--flag')
                  [ "${OPTARG:-}" ] && OPTARG=${OPTARG#*\=} && set "noarg" "$1" && break
                  eval '[ ${OPTARG+x} ] &&:' && OPTARG='1' || OPTARG=''
                  FLAG="$OPTARG"
                  ;;
                '-p'|'--param')
                  [ $# -le 1 ] && set "required" "$1" && break
                  OPTARG=$2
                  PARAM="$OPTARG"
                  shift ;;
                '-o'|'--option')
                  set -- "$1" "$@"
                  [ ${OPTARG+x} ] && {
                    case $1 in --no-*|--without-*) set "noarg" "${1%%\=*}"; break; esac
                    [ "${OPTARG:-}" ] && { shift; OPTARG=$2; } || OPTARG='default'
                  } || OPTARG=''
                  OPTION="$OPTARG"
                  shift ;;
                '-h'|'--help')
                  usage
                  exit 0 ;;
                '--version')
                  echo "${VERSION}"
                  exit 0 ;;
                --)
                  shift
                  while [ $# -gt 0 ]; do
                    REST="${REST} \"\${$(($OPTIND-$#))}\""
                    shift
                  done
                  break ;;
                [-]?*) set "unknown" "$1"; break ;;
                *)
                  REST="${REST} \"\${$(($OPTIND-$#))}\""
              esac
              shift
            done
            [ $# -eq 0 ] && { OPTIND=1; unset OPTARG; return 0; }
            case $1 in
              unknown) set "Unrecognized option: $2" "$@" ;;
              noarg) set "Does not allow an argument: $2" "$@" ;;
              required) set "Requires an argument: $2" "$@" ;;
              pattern:*) set "Does not match the pattern (${1#*:}): $2" "$@" ;;
              notcmd) set "Not a command: $2" "$@" ;;
              *) set "Validation error ($1): $2" "$@"
            esac
            echo "$1" >&2
            exit 1
          }
          usage() {
          cat<<'GETOPTIONSHERE'
          Usage: example.sh [options]... [arguments]...
          
          Options:
            -f, --flag                  takes no arguments
            -p, --param PARAM           takes one argument
            -o, --option[=OPTION]       takes one optional argument
            -h, --help
                --version
          GETOPTIONSHERE
          }
          

          【讨论】:

            【解决方案7】:

            我用它从末尾迭代 key => value。第一个可选参数在循环之后被捕获。

            用法是./script.sh optional-first-arg -key value -key2 value2

            #!/bin/sh
            
            a=$(($#-1))
            b=$(($#))
            while [ $a -gt 0 ]; do
                eval 'key="$'$a'"; value="$'$b'"'
                echo "$key => $value"
                b=$(($b-2))
                a=$(($a-2))
            done
            unset a b key value
            
            [ $(($#%2)) -ne 0 ] && echo "first_arg = $1"
            
            

            当然,您可以从左到右进行一些更改。

            此 sn-p 代码显示 key => value 对和第一个参数(如果存在)。

            #!/bin/sh
            
            a=$((1+$#%2))
            b=$((1+$a))
            
            [ $(($#%2)) -ne 0 ] && echo "first_arg = $1"
            
            while [ $a -lt $# ]; do
                eval 'key="$'$a'"; value="$'$b'"'
                echo "$key => $value"
                b=$(($b+2))
                a=$(($a+2))
            done
            
            unset a b key value
            
            

            使用 100,000 个参数进行测试,速度很快。

            你也可以从左到右迭代 key => valuefirst optional arg 而不需要 eval :

            #!/bin/sh
            
            a=$(($#%2))
            b=0
            
            [ $a -eq 1 ] && echo "first_arg = $1"
            
            for value; do
                if [ $b -gt $a -a $(($b%2)) -ne $a ]; then
                    echo "$key => $value"
                fi
                key="$value"
                b=$((1+$b))
            done
            
            unset a b key value
            
            

            【讨论】:

              【解决方案8】:

              我最终实现了accepted answerdash(或/bin/sh)版本,基本上没有使用数组:

              while [[ $# -gt 0 ]]; do
                  case "$1" in
                  -v|--verbose) verbose=1; shift;;
                  -o|--output) if [[ $# -gt 1 && "$2" != -* ]]; then
                          file=$2; shift 2
                      else
                          echo "-o requires file-path" 1>&2; exit 1
                      fi ;;
                  --)
                      while [[ $# -gt 0 ]]; do BACKUP="$BACKUP;$1"; shift; done
                      break;;
                  *)
                      BACKUP="$BACKUP;$1"
                      shift
                      ;;
                  esac
              done
              # Restore unused arguments.
              while [ -n "$BACKUP" ] ; do
                  [ ! -z "${BACKUP%%;*}" ] && set -- "$@" "${BACKUP%%;*}"
                  [ "$BACKUP" = "${BACKUP/;/}" ] && break
                  BACKUP="${BACKUP#*;}"
              done
              

              【讨论】:

                【解决方案9】:

                根据这里的其他答案,这是我的版本:

                #!/bin/bash
                set -e
                
                function parse() {
                    for arg in "$@"; do # transform long options to short ones
                        shift
                        case "$arg" in
                            "--name") set -- "$@" "-n" ;;
                            "--verbose") set -- "$@" "-v" ;;
                            *) set -- "$@" "$arg"
                        esac
                    done
                
                    while getopts "n:v" optname
                    do
                        case "$optname" in
                            "n") name=${OPTARG} ;;
                            "v") verbose=true ;;
                        esac
                    done
                    shift "$((OPTIND-1))" # shift out all the already processed options
                }
                
                
                parse "$@"
                echo "hello $name"
                if [ ! -z $verbose ]; then echo 'nice to meet you!'; fi
                

                用法:

                $ ./parse.sh
                hello
                $ ./parse.sh -n YOUR_NAME
                hello YOUR_NAME
                $ ./parse.sh -n YOUR_NAME -v
                hello YOUR_NAME
                nice to meet you!
                $ ./parse.sh -v -n YOUR_NAME
                hello YOUR_NAME
                nice to meet you!
                $ ./parse.sh -v
                hello 
                nice to meet you!
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2011-04-27
                  • 2010-09-26
                  • 2014-12-18
                  • 1970-01-01
                  相关资源
                  最近更新 更多