【问题标题】:How to get arguments with flags in Bash如何在 Bash 中获取带有标志的参数
【发布时间】:2011-10-27 12:49:46
【问题描述】:

我知道我可以在 bash 中轻松获取这样的定位参数:

$0$1

我希望能够使用这样的标志选项来指定每个参数的用途:

mysql -u user -h host

通过标志而不是位置获取-u param 值和-h param 值的最佳方法是什么?

【问题讨论】:

  • unix.stackexchange.com 询问/检查可能是个好主意
  • google for "bash getopts" -- 很多教程。
  • @glenn-jackman:既然我知道了这个名字,我肯定会用谷歌搜索它。关于谷歌的事情是 - 问一个问题 - 你应该已经知道 50% 的答案。
  • 看看BashFAQ#035

标签: bash shell


【解决方案1】:

这是我常用的成语:

while test $# -gt 0; do
  case "$1" in
    -h|--help)
      echo "$package - attempt to capture frames"
      echo " "
      echo "$package [options] application [arguments]"
      echo " "
      echo "options:"
      echo "-h, --help                show brief help"
      echo "-a, --action=ACTION       specify an action to use"
      echo "-o, --output-dir=DIR      specify a directory to store output in"
      exit 0
      ;;
    -a)
      shift
      if test $# -gt 0; then
        export PROCESS=$1
      else
        echo "no process specified"
        exit 1
      fi
      shift
      ;;
    --action*)
      export PROCESS=`echo $1 | sed -e 's/^[^=]*=//g'`
      shift
      ;;
    -o)
      shift
      if test $# -gt 0; then
        export OUTPUT=$1
      else
        echo "no output dir specified"
        exit 1
      fi
      shift
      ;;
    --output-dir*)
      export OUTPUT=`echo $1 | sed -e 's/^[^=]*=//g'`
      shift
      ;;
    *)
      break
      ;;
  esac
done

重点是:

  • $# 是参数的数量
  • while 循环查看提供的所有参数,并在 case 语句中匹配它们的值
  • shift 带走了第一个。您可以在 case 语句中多次移动以获取多个值。

【讨论】:

  • --action*--output-dir* 案例有什么作用?
  • 他们只是将获得的值保存到环境中。
  • @Lucio 超级旧评论,但添加它以防其他人访问此页面。 *(通配符)适用于有人键入--action=[ACTION] 的情况以及有人键入--action [ACTION] 的情况
  • 为什么*) 你会在那里中断,你不应该退出或忽略错误的选项吗?换句话说,-bad -o dir -o dir 部分永远不会被处理。
  • 您可以将export PROCESS='echo $1 | sed -e 's/^[^=]*=//g' 替换为export PROCESS="${1/*"="/}"
【解决方案2】:

getopt 是你的朋友.. 一个简单的例子:

function f () {
TEMP=`getopt --long -o "u:h:" "$@"`
eval set -- "$TEMP"
while true ; do
    case "$1" in
        -u )
            user=$2
            shift 2
        ;;
        -h )
            host=$2
            shift 2
        ;;
        *)
            break
        ;;
    esac 
done;

echo "user = $user, host = $host"
}

f -u myself -h some_host

你的 /usr/bin 目录下应该有各种各样的例子。

【讨论】:

  • 可以在目录/usr/share/doc/util-linux/examples 中找到更广泛的示例,至少在 Ubuntu 机器上是这样。
  • @Shizzmo 你能解释一下set -- 的语法吗?这个成语我已经看过几次了。将感激不尽。非常感谢。
【解决方案3】:

本示例使用 Bash 内置的getopts 命令,来自Google Shell Style Guide

a_flag=''
b_flag=''
files=''
verbose='false'

print_usage() {
  printf "Usage: ..."
}

while getopts 'abf:v' flag; do
  case "${flag}" in
    a) a_flag='true' ;;
    b) b_flag='true' ;;
    f) files="${OPTARG}" ;;
    v) verbose='true' ;;
    *) print_usage
       exit 1 ;;
  esac
done

注意:如果一个字符后跟一个冒号(例如f:),该选项应该有一个参数。

用法示例:./script -v -a -b -f filename

与公认的答案相比,使用 getopts 有几个优点:

  • while 条件更具可读性,并显示了可接受的选项是什么
  • 更清晰的代码;不计算参数的数量和移位
  • 您可以加入选项(例如-a -b -c-abc

但是,一个很大的缺点是它不支持长选项,只支持单字符选项。

【讨论】:

  • 有人想知道为什么这个使用 bash 内置函数的答案不是最佳答案
  • 为了后代:'abf:v' 中的冒号表示 -f 需要一个额外的参数(在这种情况下是文件名)。
  • 我不得不将错误行改为:?) printf '\nUsage: %s: [-a] aflag [-b] bflag\n' $0; exit 2 ;;
  • 您能添加关于冒号的注释吗?在每个字母之后,没有冒号表示没有 arg,一个冒号表示一个 arg,两个冒号表示可选 arg?
  • @WillBarnwell 应该注意,它是在提出问题 3 年后添加的,而最佳答案是在同一天添加的。
【解决方案4】:

另一种选择是使用类似于以下示例的内容,这将允许您使用长 --image 或短 -i 标签并允许编译 -i="example.jpg" 或单独的 -i example.jpg 传入参数的方法。

# declaring a couple of associative arrays
declare -A arguments=();  
declare -A variables=();

# declaring an index integer
declare -i index=1;

# any variables you want to use here
# on the left left side is argument label or key (entered at the command line along with it's value) 
# on the right side is the variable name the value of these arguments should be mapped to.
# (the examples above show how these are being passed into this script)
variables["-gu"]="git_user";  
variables["--git-user"]="git_user";  
variables["-gb"]="git_branch";  
variables["--git-branch"]="git_branch";  
variables["-dbr"]="db_fqdn";  
variables["--db-redirect"]="db_fqdn";  
variables["-e"]="environment";  
variables["--environment"]="environment";

# $@ here represents all arguments passed in
for i in "$@"  
do  
  arguments[$index]=$i;
  prev_index="$(expr $index - 1)";

  # this if block does something akin to "where $i contains ="
  # "%=*" here strips out everything from the = to the end of the argument leaving only the label
  if [[ $i == *"="* ]]
    then argument_label=${i%=*} 
    else argument_label=${arguments[$prev_index]}
  fi

  # this if block only evaluates to true if the argument label exists in the variables array
  if [[ -n ${variables[$argument_label]} ]]
    then
        # dynamically creating variables names using declare
        # "#$argument_label=" here strips out the label leaving only the value
        if [[ $i == *"="* ]]
            then declare ${variables[$argument_label]}=${i#$argument_label=} 
            else declare ${variables[$argument_label]}=${arguments[$index]}
        fi
  fi

  index=index+1;
done;

# then you could simply use the variables like so:
echo "$git_user";

【讨论】:

    【解决方案5】:

    我认为这可以作为您想要实现的目标的更简单示例。无需使用外部工具。 Bash 内置工具可以为您完成这项工作。

    function DOSOMETHING {
    
       while test $# -gt 0; do
               case "$1" in
                    -first)
                        shift
                        first_argument=$1
                        shift
                        ;;
                    -last)
                        shift
                        last_argument=$1
                        shift
                        ;;
                    *)
                       echo "$1 is not a recognized flag!"
                       return 1;
                       ;;
              esac
      done  
    
      echo "First argument : $first_argument";
      echo "Last argument : $last_argument";
     }
    

    这将允许您使用标志,因此无论您传递参数的顺序如何,您都将获得正确的行为。

    例子:

     DOSOMETHING -last "Adios" -first "Hola"
    

    输出:

     First argument : Hola
     Last argument : Adios
    

    您可以将此功能添加到您的个人资料或将其放入脚本中。

    谢谢!

    编辑: 将其保存为文件,然后以yourfile.sh -last "Adios" -first "Hola" 执行它

    #!/bin/bash
    while test $# -gt 0; do
               case "$1" in
                    -first)
                        shift
                        first_argument=$1
                        shift
                        ;;
                    -last)
                        shift
                        last_argument=$1
                        shift
                        ;;
                    *)
                       echo "$1 is not a recognized flag!"
                       return 1;
                       ;;
              esac
      done  
    
      echo "First argument : $first_argument";
      echo "Last argument : $last_argument";
    

    【讨论】:

    • 我使用上面的代码&运行时它没有打印任何东西。 ./hello.sh DOSOMETHING -last "Adios" -first "Hola"
    • @dinu0101 这是一个函数。不是脚本。您应该将其用作 DOSOMETHING -last "Adios" -first "Hola"
    • 谢谢@Matias。明白了。如何在脚本中运行。
    • 非常感谢@Matias
    • 在 macOS 上使用 return 1; 和最后一个示例输出 can only 'return' from a function or sourced script。切换到exit 1; 可以正常工作。
    【解决方案6】:

    我喜欢 Robert McMahan 的最佳答案,因为它似乎最容易制作成可共享的包含文件以供您的任何脚本使用。但是,if [[ -n ${variables[$argument_label]} ]] 抛出消息“变量:错误的数组下标”似乎存在缺陷。我没有代表发表评论,我怀疑这是正确的“修复”,但是将 if 包装在 if [[ -n $argument_label ]] ; then 中可以清理它。

    这是我最终得到的代码,如果您知道更好的方法,请在罗伯特的回答中添加评论。

    包含文件“flags-declares.sh”

    # declaring a couple of associative arrays
    declare -A arguments=();
    declare -A variables=();
    
    # declaring an index integer
    declare -i index=1;
    

    包含文件“flags-arguments.sh”

    # $@ here represents all arguments passed in
    for i in "$@"
    do
      arguments[$index]=$i;
      prev_index="$(expr $index - 1)";
    
      # this if block does something akin to "where $i contains ="
      # "%=*" here strips out everything from the = to the end of the argument leaving only the label
      if [[ $i == *"="* ]]
        then argument_label=${i%=*}
        else argument_label=${arguments[$prev_index]}
      fi
    
      if [[ -n $argument_label ]] ; then
        # this if block only evaluates to true if the argument label exists in the variables array
        if [[ -n ${variables[$argument_label]} ]] ; then
          # dynamically creating variables names using declare
          # "#$argument_label=" here strips out the label leaving only the value
          if [[ $i == *"="* ]]
            then declare ${variables[$argument_label]}=${i#$argument_label=} 
            else declare ${variables[$argument_label]}=${arguments[$index]}
          fi
        fi
      fi
    
      index=index+1;
    done;
    

    你的“script.sh”

    . bin/includes/flags-declares.sh
    
    # any variables you want to use here
    # on the left left side is argument label or key (entered at the command line along with it's value) 
    # on the right side is the variable name the value of these arguments should be mapped to.
    # (the examples above show how these are being passed into this script)
    variables["-gu"]="git_user";
    variables["--git-user"]="git_user";
    variables["-gb"]="git_branch";
    variables["--git-branch"]="git_branch";
    variables["-dbr"]="db_fqdn";
    variables["--db-redirect"]="db_fqdn";
    variables["-e"]="environment";
    variables["--environment"]="environment";
    
    . bin/includes/flags-arguments.sh
    
    # then you could simply use the variables like so:
    echo "$git_user";
    echo "$git_branch";
    echo "$db_fqdn";
    echo "$environment";
    

    【讨论】:

      【解决方案7】:

      如果你熟悉 Python argparse,并且不介意调用 python 来解析 bash 参数,我发现有一段代码非常有用且超级好用,称为 argparse-bash https://github.com/nhoffman/argparse-bash

      示例来自他们的 example.sh 脚本:

      #!/bin/bash
      
      source $(dirname $0)/argparse.bash || exit 1
      argparse "$@" <<EOF || exit 1
      parser.add_argument('infile')
      parser.add_argument('outfile')
      parser.add_argument('-a', '--the-answer', default=42, type=int,
                          help='Pick a number [default %(default)s]')
      parser.add_argument('-d', '--do-the-thing', action='store_true',
                          default=False, help='store a boolean [default %(default)s]')
      parser.add_argument('-m', '--multiple', nargs='+',
                          help='multiple values allowed')
      EOF
      
      echo required infile: "$INFILE"
      echo required outfile: "$OUTFILE"
      echo the answer: "$THE_ANSWER"
      echo -n do the thing?
      if [[ $DO_THE_THING ]]; then
          echo " yes, do it"
      else
          echo " no, do not do it"
      fi
      echo -n "arg with multiple values: "
      for a in "${MULTIPLE[@]}"; do
          echo -n "[$a] "
      done
      echo
      

      【讨论】:

        【解决方案8】:

        我提出一个简单的 TLDR:;未启动的示例。

        创建一个名为 greeter.sh 的 bash 脚本

        #!/bin/bash
        
        while getopts "n:" arg; do
          case $arg in
            n) Name=$OPTARG;;
          esac
        done
        
        echo "Hello $Name!"
        

        然后您可以在执行脚本时传递一个可选参数-n

        这样执行脚本:

        $ bash greeter.sh -n 'Bob'
        

        输出

        $ Hello Bob!
        

        备注

        如果您想使用多个参数:

        1. 使用更多参数扩展while getops "n:" arg: do,例如 while getops "n:o:p:" arg: do
        2. 使用额外的变量分配扩展 case 开关。如o) Option=$OPTARGp) Parameter=$OPTARG

        使脚本可执行:

        chmod u+x greeter.sh
        

        【讨论】:

          【解决方案9】:
          #!/bin/bash
          
          if getopts "n:" arg; then
            echo "Welcome $OPTARG"
          fi
          

          另存为 sample.sh 并尝试运行

          sh sample.sh -n John
          

          在您的终端中。

          【讨论】:

            【解决方案10】:

            我在使用带有多个标志的 getopts 时遇到了麻烦,所以我编写了这段代码。它使用模态变量来检测标志,并使用这些标志将参数分配给变量。

            请注意,如果标志不应该有参数,则可以执行设置 CURRENTFLAG 以外的其他操作。

                for MYFIELD in "$@"; do
            
                    CHECKFIRST=`echo $MYFIELD | cut -c1`
            
                    if [ "$CHECKFIRST" == "-" ]; then
                        mode="flag"
                    else
                        mode="arg"
                    fi
            
                    if [ "$mode" == "flag" ]; then
                        case $MYFIELD in
                            -a)
                                CURRENTFLAG="VARIABLE_A"
                                ;;
                            -b)
                                CURRENTFLAG="VARIABLE_B"
                                ;;
                            -c)
                                CURRENTFLAG="VARIABLE_C"
                                ;;
                        esac
                    elif [ "$mode" == "arg" ]; then
                        case $CURRENTFLAG in
                            VARIABLE_A)
                                VARIABLE_A="$MYFIELD"
                                ;;
                            VARIABLE_B)
                                VARIABLE_B="$MYFIELD"
                                ;;
                            VARIABLE_C)
                                VARIABLE_C="$MYFIELD"
                                ;;
                        esac
                    fi
                done
            

            【讨论】:

              【解决方案11】:

              所以这是我的解决方案。我希望能够处理不带连字符、带一个连字符和带两个连字符的布尔标志,以及带一个和两个连字符的参数/值分配。

              # Handle multiple types of arguments and prints some variables
              #
              # Boolean flags
              # 1) No hyphen
              #    create   Assigns `true` to the variable `CREATE`.
              #             Default is `CREATE_DEFAULT`.
              #    delete   Assigns true to the variable `DELETE`.
              #             Default is `DELETE_DEFAULT`.
              # 2) One hyphen
              #      a      Assigns `true` to a. Default is `false`.
              #      b      Assigns `true` to b. Default is `false`.
              # 3) Two hyphens
              #    cats     Assigns `true` to `cats`. By default is not set.
              #    dogs     Assigns `true` to `cats`. By default is not set.
              #
              # Parameter - Value
              # 1) One hyphen
              #      c      Assign any value you want
              #      d      Assign any value you want
              #
              # 2) Two hyphens
              #   ... Anything really, whatever two-hyphen argument is given that is not
              #       defined as flag, will be defined with the next argument after it.
              #
              # Example:
              # ./parser_example.sh delete -a -c VA_1 --cats --dir /path/to/dir
              parser() {
                  # Define arguments with one hyphen that are boolean flags
                  HYPHEN_FLAGS="a b"
                  # Define arguments with two hyphens that are boolean flags
                  DHYPHEN_FLAGS="cats dogs"
              
                  # Iterate over all the arguments
                  while [ $# -gt 0 ]; do
                      # Handle the arguments with no hyphen
                      if [[ $1 != "-"* ]]; then
                          echo "Argument with no hyphen!"
                          echo $1
                          # Assign true to argument $1
                          declare $1=true
                          # Shift arguments by one to the left
                          shift
                      # Handle the arguments with one hyphen
                      elif [[ $1 == "-"[A-Za-z0-9]* ]]; then
                          # Handle the flags
                          if [[ $HYPHEN_FLAGS == *"${1/-/}"* ]]; then
                              echo "Argument with one hyphen flag!"
                              echo $1
                              # Remove the hyphen from $1
                              local param="${1/-/}"
                              # Assign true to $param
                              declare $param=true
                              # Shift by one
                              shift
                          # Handle the parameter-value cases
                          else
                              echo "Argument with one hyphen value!"
                              echo $1 $2
                              # Remove the hyphen from $1
                              local param="${1/-/}"
                              # Assign argument $2 to $param
                              declare $param="$2"
                              # Shift by two
                              shift 2
                          fi
                      # Handle the arguments with two hyphens
                      elif [[ $1 == "--"[A-Za-z0-9]* ]]; then
                          # NOTE: For double hyphen I am using `declare -g $param`.
                          #   This is the case because I am assuming that's going to be
                          #   the final name of the variable
                          echo "Argument with two hypens!"
                          # Handle the flags
                          if [[ $DHYPHEN_FLAGS == *"${1/--/}"* ]]; then
                              echo $1 true
                              # Remove the hyphens from $1
                              local param="${1/--/}"
                              # Assign argument $2 to $param
                              declare -g $param=true
                              # Shift by two
                              shift
                          # Handle the parameter-value cases
                          else
                              echo $1 $2
                              # Remove the hyphens from $1
                              local param="${1/--/}"
                              # Assign argument $2 to $param
                              declare -g $param="$2"
                              # Shift by two
                              shift 2
                          fi
                      fi
              
                  done
                  # Default value for arguments with no hypheb
                  CREATE=${create:-'CREATE_DEFAULT'}
                  DELETE=${delete:-'DELETE_DEFAULT'}
                  # Default value for arguments with one hypen flag
                  VAR1=${a:-false}
                  VAR2=${b:-false}
                  # Default value for arguments with value
                  # NOTE1: This is just for illustration in one line. We can well create
                  #   another function to handle this. Here I am handling the cases where
                  #   we have a full named argument and a contraction of it.
                  #   For example `--arg1` can be also set with `-c`.
                  # NOTE2: What we are doing here is to check if $arg is defined. If not,
                  #   check if $c was defined. If not, assign the default value "VD_"
                  VAR3=$(if [[ $arg1 ]]; then echo $arg1; else echo ${c:-"VD_1"}; fi)
                  VAR4=$(if [[ $arg2 ]]; then echo $arg2; else echo ${d:-"VD_2"}; fi)
              }
              
              
              # Pass all the arguments given to the script to the parser function
              parser "$@"
              
              
              echo $CREATE $DELETE $VAR1 $VAR2 $VAR3 $VAR4 $cats $dir
              

              一些参考资料

              • 发现主程序here
              • 更多关于将所有参数传递给函数here
              • 更多关于默认值的信息here
              • 更多关于declare$ bash -c "help declare"的信息。
              • 更多关于shift的信息$ bash -c "help shift"

              【讨论】:

                猜你喜欢
                • 2021-09-28
                • 1970-01-01
                • 2020-06-15
                • 2016-01-11
                • 1970-01-01
                • 2022-01-17
                • 2012-07-21
                • 1970-01-01
                • 2010-12-15
                相关资源
                最近更新 更多