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

Is there a way to continue in a Flag when there is no $OPTARG set in Bash, GETOPTS?

我想用 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. -Plast 选项。在这种情况下,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 -- 它更冗长,但您有 finer-grained 控制权

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

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

while true; do
    case "" in
       -d)  echo d; shift ;;
       -B)  echo B; shift ;;
       -h)  echo h; shift ;;
       -m)  echo m; shift ;;
       -t)  echo t; shift ;;
      --P)  case "" in
               '')  echo "P with no argument" ;;
                *)  echo "P: " ;;
            esac
            shift 2
            ;;
      --b)  case "" in
               '')  echo "b with no argument" ;;
                *)  echo "b: " ;;
            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
  • 不要将逻辑与参数解析混在一起。
  • 首选小写变量。

My aim is to continue at my comment, when there is no $OPTARG set for -P

我建议不要。你在一个范围内做的越少,你需要考虑的就越少。在不同的阶段拆分解析选项和执行操作。我建议:

# 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

“不要将 编程 应用程序逻辑放在案例分支中”的示例 -- 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 语句的大小很容易膨胀到无法维护。