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

How to ignore invalid arguments with getopts and continue parsing?

我有以下简单代码:

#!/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 只是在找到一些未知的 non-option 参数 (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.

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

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

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

###############################################################################
#
# Convenience method to test if a command line option is present.
#
# Parameters:
#    - command line argument to match against
#    - 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=""
    # remove  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;
}