当Bash,GETOPTS中没有设置$ OPTARG时,是否有办法在Flag中继续?

问题描述

我想用getopts构建一个脚本,当未设置$ OPTARG时,该脚本在标志中继续。 我的脚本如下:

OPTIONS=':dBhmtb:P:'
while getopts $OPTIONS OPTION
do
        case "$OPTION" in
                m ) echo "m"          
                t ) echo "t"     
                d ) echo "d";;     
                h ) echo "h";; 
                B ) echo "b";;
                r ) echo "r";; 
                b ) echo "b"                                 
                P ) echo hi;; 
                    #continue here                    
                \? ) echo "?";;
                :) echo "test -$OPTARG requieres an argument" >&2
                         
        esac
done

当没有为-P设置$ OPTARG时,我的目的是继续我的评论。 运行./test -P后,我得到的是: test -P requieres an argument 然后它在循环后继续,但是我想在-P标志中继续。 全清? 有任何想法吗?

解决方法

首先,修复某些案例分支中缺少的;;

我认为您不能:您告诉getopts -P需要一个参数:两个错误情况

  1. -P(不带参数)是 last 选项。在这种情况下,getops看到-P之后没有任何内容,并将OPTION变量设置为:,您可以在case语句中进行处理。

  2. -P之后是另一个选项:即使下一个单词是另一个选项,getopts也将简单地接受下一个单词,例如OPTARG。

    将案例分支更改为

    P ) echo "P: '$OPTARG'";;
    

    然后:

    • 调用bash script.sh -P -m -t之类的脚本,输出为
      P: '-m'
      t
      
    • 调用bash script.sh -Pmt之类的脚本,输出为
      P: 'mt'
      

    这显然很难解决。您怎么知道用户是否打算将选项参数实际上是“ mt”而不是选项-m和-t?

您也许可以使用getopt(请参阅the canonical example)来解决此问题,为长选项(那些需要与--long=value等号的可选参数)使用可选参数检查选项参数是否丢失。


getopts解析为getopt-比较冗长,但是您拥有更细粒度的控制

die() { echo "$*" >&2; exit 1; }

tmpArgs=$(getopt -o 'dBhmt' \
                 --long 'b::,P::' \
                 -n "$(basename "$0")" \
                 -- "$@"
        )
(( $? == 0 )) || die 'Problem parsing options'
eval set -- "$tmpArgs"

while true; do
    case "$1" in
       -d)  echo d; shift ;;
       -B)  echo B; shift ;;
       -h)  echo h; shift ;;
       -m)  echo m; shift ;;
       -t)  echo t; shift ;;
      --P)  case "$2" in
               '')  echo "P with no argument" ;;
                *)  echo "P: $2" ;;
            esac
            shift 2
            ;;
      --b)  case "$2" in
               '')  echo "b with no argument" ;;
                *)  echo "b: $2" ;;
            esac
            shift 2
            ;;
       --)  shift; break ;;
        *)  printf "> %q\n" "$@"
            die 'getopt internal error: $*' ;;
    esac
done

echo "Remaining arguments:"
for ((i=1; i<=$#; i++)); do
    echo "$i: ${!i}"
done

通过--P成功调用程序:

$ ./myscript.sh --P -mt foo bar
P with no argument
m
t
Remaining arguments:
1: foo
2: bar
$ ./myscript.sh --P=arg -mt foo bar
P: arg
m
t
Remaining arguments:
1: foo
2: bar

这确实会给您的用户带来更高的开销,因为-P(带短划线)无效,并且参数必须以=给出

$ ./myscript.sh --P arg -mt foo bar
P with no argument
m
t
Remaining arguments:
1: arg
2: foo
3: bar
$ ./myscript.sh --Parg mt foo bar
myscript.sh: unrecognized option `--Parg'
Problem parsing options
$ ./myscript.sh -P -mt foo bar
myscript.sh: invalid option -- P
Problem parsing options
$ ./myscript.sh -P=arg -mt foo bar
myscript.sh: invalid option -- P
myscript.sh: invalid option -- =
myscript.sh: invalid option -- a
myscript.sh: invalid option -- r
myscript.sh: invalid option -- g
Problem parsing options
,
  • 请勿将逻辑与参数解析混在一起。
  • 首选小写变量。

当没有为-P设置$ OPTARG时,我的目的是继续我的评论

我建议不要这样做。您在一个范围内所做的事情越少,您所考虑的就越少。将解析选项和执行操作分开在不同的阶段。我建议:

# set default values for options
do_something_related_to_P=false
recursive=false
tree_output=false

# parse arguments
while getopts ':dBhmtb:P:' option; do
        case "$option" in
                t) tree_output=true; ;;
                r) recursive="$OPTARG"; ;;
                P) do_something_related_to_P="$OPTARG"; ;;
                \?) echo "?";;
                :) echo "test -$OPTARG requieres an argument" >&2
                         
        esac
done

# application logic
if "$do_something_related_to_P"; then
    do something related to P
    if "$recursive"; then
        do it in recursive style
    fi
fi |
if "$tree_output"; then
    output_as_tree
else
    cat
fi
,

“不要在大小写分支中放置 programming 应用程序逻辑”的示例-touch命令可以使用-t timespec选项或-r referenceFile选项,但不能同时使用:

$ touch -t 202010100000 -r file1 file2
touch: cannot specify times from more than one source
Try 'touch --help' for more information.

我会这样实现(忽略其他选项):

while getopts t:r: opt; do
    case $opt in
        t) timeSpec=$OPTARG ;;
        r) refFile=$OPTARG ;;
    esac
done
shift $((OPTIND-1))

if [[ -n $timeSpec && -n $refFile ]]; then
    echo "touch: cannot specify times from more than one source" >&2
    exit 1
fi

我会这样做:

while getopts t:r: opt; do
    case $opt in
        t)  if [[ -n $refFile ]]; then
                echo "touch: cannot specify times from more than one source" >&2
                exit 1
            fi
            timeSpec=$OPTARG ;;
        r)  if [[ -n $timeSpec ]]; then
                echo "touch: cannot specify times from more than one source" >&2
                exit 1
            fi
            refFile=$OPTARG ;;
    esac
done

您可以看到逻辑是否变​​得更加复杂(正如我所提到的,恰好是-a或-b或-c之一),所以case语句的大小很容易就无法维持。