【问题标题】:Bash : Parse options after arguments with getoptsBash:使用 getopts 解析参数后的选项
【发布时间】:2021-04-29 08:07:36
【问题描述】:

在请求一些参数 (arg) 和选项 (-a) 的脚本中,我想让脚本用户可以将选项放置在命令行中他想要的位置。

这是我的代码:

while getopts "a" opt; do
  echo "$opt"
done
shift $((OPTIND-1))

echo "all_end : $*"

有了这个订单,我就有了预期的行为:

./test.sh -a arg
a
all_end : arg

我想用这个订单得到同样的结果:

./test.sh arg -a
all_end : arg -a

【问题讨论】:

  • 大部分 linux 命令都支持这个特性。我不希望我的用户丢失。示例:tail file -n 5tail -n 5 file 有效。
  • 这和“tail 5 -n”不同,这是你想要的,这是不可能的
  • 我认为您必须首先修改$@ 中的位置参数并删除所有非选项参数。 getopts 本身无法支持您的要求。
  • 大多数 linux 命令不是用 Bourne shell 编写的,而是使用类似 GNU getops_long 库调用选项处理(或完全不同的东西;大多数语言都有自己的标准——或多个——选项处理模块)。您当然可以在 shell 脚本中编写自己的选项处理例程来执行您想要的操作。
  • Complex option parsing 中描述了一种重新排列的解决方案。

标签: bash getopts


【解决方案1】:

getopt 命令(util-linux 包的一部分,与 getopts 不同)将执行您想要的操作。 bash faq 对使用它有一些意见,但老实说,如今大多数系统都将拥有现代版本的 getopt

考虑以下示例:

#!/bin/sh

options=$(getopt -o o: --long option: -- "$@")
eval set -- "$options"

while :; do
    case "$1" in
        -o|--option)
            shift
            OPTION=$1
            ;;
        --)
            shift
            break
            ;;
    esac

    shift
done

echo "got option: $OPTION"
echo "remaining args are: $@"

我们可以这样称呼:

$ ./options.sh -o foo arg1 arg2
got option: foo
remaining args are: arg1 arg2

或者像这样:

$ ./options.sh  arg1 arg2 -o foo
got option: foo
remaining args are: arg1 arg2

【讨论】:

    【解决方案2】:

    您仍然可以通过查看每个参数来进行参数解析

    #!/bin/bash
    
    for i in "$@"
    do
    case $i in
        -a)
        ARG1="set"
        shift
        ;;
        *)
        # the rest (not -a)
        ARGS="${ARGS} $i"
        ;;
    esac
    done
    
    if [ -z "$ARG1" ]; then
      echo "You haven't passed -a"
    else
      echo "You have passed -a"
    fi
    
    echo "You have also passed: ${ARGS}"
    

    然后你会得到:

    > ./script.sh arg -a
    You have passed -a
    You have also passed:  arg
    > ./script.sh -a arg
    You have passed -a
    You have also passed:  arg
    > ./script.sh -a
    You have passed -a
    You have also passed:
    > ./script.sh arg
    You haven't passed -a
    You have also passed:  arg
    

    【讨论】:

      【解决方案3】:

      考虑这种方法

      #!/bin/bash
          
      opt(){
          case $1 in
              -o|--option) option="$2";;
              -h|--help  ) echo "$help"; exit 0;;
                        *) echo "unknown option: $1"; exit 1;;
          esac
      }
      
      while [[ $@ ]]; do
          case $1 in
            arg1) var1=$1 ;;
            arg2) var2=$1 ;;
              -*) opt "$@"; shift;;
               *) echo "unknown option: $1"; exit 1;;
          esac
          shift
      done
      
      echo args: $var1 $var2
      echo opts: $option
      

      【讨论】:

        【解决方案4】:

        在 bash 编程的多年中,我发现摆脱脑海中认为函数应该看起来像 f(x,y) 的想法很有用,尤其是因为 bash 需要笨拙/低效的代码来处理命令行参数。

        选项参数通常具有默认值,并且可以更容易地作为环境变量传递,其范围仅限于被调用的脚本,并保存必须提供的参数。

        将此应用于您的示例,脚本将如下所示:

        OPTION=${OPTION:="fee"}
        echo "option: $OPTION"
        echo "remaining args are: $*"
        

        并且会被调用:

        OPTION="foo" ./options.sh arg1 arg2
        

        【讨论】: