如何使用 getopts 忽略无效参数并继续解析?

Posted

技术标签:

【中文标题】如何使用 getopts 忽略无效参数并继续解析?【英文标题】:How to ignore invalid arguments with getopts and continue parsing? 【发布时间】:2016-04-04 19:36:39 【问题描述】:

我有以下简单的代码:

#!/usr/bin/env bash
while getopts :f arg; do
  case $arg in
    f) echo Option $arg specified. ;;
    *) echo Unknown option: $OPTARG. ;;
  esac
done

它适用于简单的场景,例如:

$ ./test.sh -f
Option f specified.
$ ./test.sh -a -f
Unknown option: a.
Option f specified.

但它不适用于以下情况:

$ ./test.sh foo -f

$ ./test.sh -a abc -f
Unknown option: a.

如何修复上述代码示例以支持无效参数?

【问题讨论】:

“支持无效参数”是什么意思? getopts 在第一个非选项处停止解析(例如 fooabc)。你想让它做什么?不这样做并解析所有内容? 支持无效参数,以识别-f 尽管指定了未知参数,所以-a abc -f 仍会识别-f,但似乎getopts 一旦无效就停止处理找到参数。 它在第一个非选项参数处停止(即abc)。例如,-a -a -a -a -f 将打印四次 unknown。 @EtanReisner 在我看来,它停止很奇怪。实际上,对于涉及多次调用getopts 的更扩展的脚本,我确实需要它(但我不想让它变得复杂)。因此,第一次调用处理所有已知选项,进一步调用仅检查特定参数,但它们失败了,因为它们无法识别先前的参数(我不在乎进一步调用)。 同样,未知选项不是问题。非选项参数(不以- 开头的参数)是“问题”。在第一个非选项参数上停止是 POSIX 指定的行为。这对您来说只是一个问题,因为您正在解析相同的参数以寻找不同的有效选项。如果你只是告诉每个 getopts 调用关于每个参数(但实际上只对你关心的参数做任何事情)你不应该有问题。 【参考方案1】:

这个主题帮助我找到了我在尝试检测命令行上是否存在特定参数时正在寻找的答案。我以不同的方式实现它,所以我想我会分享我的解决方案。 代码中包含注释,希望有助于理解此实现。 出于调试目的,还包括一些注释掉的行。

###############################################################################
#
# Convenience method to test if a command line option is present.
#
# Parameters:
#   $1 - command line argument to match against
#   $2 - command line parameters
#
# Example:
#   is_cmd_line_option_present "v" "$@" 
#       check if the -v option has been provided on the command line
#
###############################################################################
function is_cmd_line_option_present()

    _iclop_option="$1"
    # remove $1 from the arguments (via the shift command) to this method before searching for it from the actual command line
    shift
    # Default the return value to zero
    _iclop_return=0

    # Don't need to increment OPTIND each time, as the getopts call does that for us
    for (( OPTIND=1; OPTIND <= $#; ))
    do
        # Use getopts to parse each command line argument, and test for a match
        if getopts ":$_iclop_option" _iclop_option_var; then
            if [ "$_iclop_option_var" == "$_iclop_option" ]; then
                _iclop_return=1
             # else
                # (>&2 echo -e "[Std Err]: is_cmd_line_option_present - Option discarded _iclop_option_var: [$_iclop_option_var]")
            fi
         else
            # (>&2 echo -e "[Std Err]: is_cmd_line_option_present - Unknown Option Parameter _iclop_option_var: [$_iclop_option_var]")
            # Need to increment the option indicator when an option is found that isn't listed as an expected option, as getopts won't do this for us.
            ((OPTIND++))
        fi 
    done

    # (>&2 echo -e "[Std Err]: is_cmd_line_option_present end - _iclop_return: [$_iclop_return]")
    return $_iclop_return;

【讨论】:

【参考方案2】:

以下是一个非常不通用的解决方法,它有自己的问题,但至少在我自己的用例中有效。我怀疑 OP 的问题提供了一个旨在展示问题的最小示例,因此它可能不适用于“真实”问题。

params=0
 while getopts :f arg; do
  params=1
  case $arg in
    f) echo Option $arg specified. ;;
    *) echo Unknown option: $OPTARG. ;;
  esac
done
if [[ ! $@ == '' ]] && ((params == 0 )); then
    echo "wrong arguments"
    exit 1
fi

【讨论】:

更通用的是在while循环中增加params,然后检查params是否等于$@中“-[aA-zZ]”形式的子字符串数 我喜欢这个比“loop-in-a-loop”选项更好,而且它相当简单。【参考方案3】:

似乎getopts 只是在找到一些未知的非选项参数 (abc) 后退出循环。

我通过将getopts 循环包装到另一个循环中找到了以下解决方法:

#!/usr/bin/env bash
while :; do
  while getopts :f arg; do
    case $arg in
      f)
        echo Option $arg specified.
        ;;
      *)
        echo Unknown option: $OPTARG.
        ;;
    esac
  done
  ((OPTIND++)) 
  [ $OPTIND -gt $# ] && break
done

然后跳过无效参数并在达到最大参数时中断循环。

输出:

$ ./test.sh abc -f
Option f specified.
$ ./test.sh -a abc -f
Unknown option: a.
Option f specified.

【讨论】:

-a 不是问题。 non-option 值是。从示例中删除fooabc,它们就可以工作(就像在您的原始示例中一样)。这就是getopts 的作用方式。 您真的关心这些示例中的abc 值吗?因为这个双循环意味着你必须手动遍历参数再次并手动找到它(通过自己模拟getopts逻辑)。 @EtanReisner 当时我不关心 abc 等未知参数,因为我可以在脚本的不同位置再次阅读。

以上是关于如何使用 getopts 忽略无效参数并继续解析?的主要内容,如果未能解决你的问题,请参考以下文章

如何在雪花中使用 parse_xml 忽略损坏的 xml 行

shell 脚本参数解析之 getopt getopts

Linux getopt/getopts解析命令行参数教程

使用多个参数解析命令行选项 [getopt?]

PHP使用getopt()解析cli参数

命令行参数解析函数getopt和getopt_long函数