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

Posted

技术标签:

【中文标题】当 Bash 和 GETOPTS 中没有设置 $OPTARG 时,有没有办法在 Flag 中继续?【英文标题】:Is there a way to continue in a Flag when there is no $OPTARG set in Bash, GETOPTS? 【发布时间】:2020-10-15 13:41:29 【问题描述】:

我想构建一个带有 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 标志中继续。 全清? 有什么想法吗?

【问题讨论】:

如果您能在看到“-P”标志时提供您想要实现的行为的简短描述,将会有所帮助。 '-P $OPTARG' 标志应该初始化一个变量,但是当 -P $OPTARG 没有设置时,脚本应该寻找另一个没有在循环中设置的变量并且当它们都没有初始化时,脚本应该退出。 强烈建议不要在case分支中放入太多逻辑:只设置变量,然后设置默认值或其他错误情况的逻辑(例如您最多可以指定一个-a或 -b 或 -c) 应在处理完选项后进行编码。 你能举个例子吗? then it continues after the loop but I want to continue in the -P flag“继续-P标志”是什么意思? 【参考方案1】:

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

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

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

    -P 后面跟着另一个选项:getopts 将简单地取下一个词,即使下一个词是另一个选项,作为 OPTARG。

    将case分支改为

    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

【讨论】:

1.抱歉丢失了“;;”在案例分支中,我当然没有忘记这些。 2.看这个例子对我没有帮助,也许是因为我是新手。你能试着用我上面的评论来解释一下吗?【参考方案2】: 不要将逻辑与参数解析混为一谈。 首选小写变量。

当没有为 -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

【讨论】:

好的,我明白你想要这样做,但问题是上面的这个东西只是一个大脚本的剪切,我想避免,放置命令和循环结束,因为-P 选项只是众多选项之一,当我使用另一个标志运行脚本时,脚本总是执行这些命令。【参考方案3】:

“不要将 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 语句的大小很容易膨胀到无法维护。

【讨论】:

我也可以使用您的第二个示例吗?我需要它在这些选项中。 您有 2 位经验丰富的人告诉您“不要这样做”,但您是必须接受结果的人。当然还有穷人,当你继续前进时,他们将不得不维持它。

以上是关于当 Bash 和 GETOPTS 中没有设置 $OPTARG 时,有没有办法在 Flag 中继续?的主要内容,如果未能解决你的问题,请参考以下文章

在 Bash 中使用 getopts 检索单个选项的多个参数

如何使用内置 Bash getopts 的长选项?

如何在 bash 中结合 getopts 和位置参数? [复制]

Bash getopts,参数没有被传递给选项

在 Bash 函数中使用 getopts

在 getopts 之后解析参数