你能让 bash 脚本的选项参数成为可选的吗?

你能让 bash 脚本的选项参数成为可选的吗?

我希望这些输入中的任何一个都能起作用。也就是说,-n选项本身是可选的 - 我已经知道如何做到这一点 - 但它可能在顶部有一个可选参数。如果未给出参数,则将应用后备值。

command -n 100
command -n

我只能使前一种输入类型或后者起作用,但不能同时使两者都起作用。

HAS_NICE_THINGS=0
NICE_THINGS=50       # default value.

while getopts n: option; do
#while getopts n option; do    # NICE_THINGS would always be that default value.
#while getopts nn: option; do    # same.
    case "${option}" in
    n)
        HAS_NICE_THINGS=1
        if [[ ! -z "${OPTARG}" ]] && (( "${OPTARG}" > 0 )) && (( "${OPTARG}" <= 100 )); then
            NICE_THINGS=${OPTARG}
        fi;;
    esac
done

# error message:
# option requires an argument -- n

我还不完全确定我的脚本是否需要一个布尔值,但到目前为止,为了以防万一,我正在记录一个 ( HAS_NICE_THINGS)。

我的最终目标是在最终保存图像时设置 JPG 质量。不过,我可以想象这种结构在其他地方也很有用。

我正在使用Ubuntu 18.04.5GNU bash, version 4.4.20(1)-release (x86_64-pc-linux-gnu)

答案1

对于 Bash's/POSIX 来说不太明智getopts,但是您可以使用 util-linux 或 Busybox 中的“增强” getopt(没有 s)来做到这一点。 (仅此而已,特别是许多“传统”getopt实现也被破坏了。空格也被破坏了)

手册页说getopts

可选字符串包含要识别的选项字符;如果一个字符后面跟着一个冒号,则该选项应该有一个参数,该参数应该用空格与其分隔。

没有提到可选的选项参数。

当然,您可以有另一个可选选项来提供非默认值。例如,让我们-n不带任何参数,只启用好东西,让我们-N <arg>带参数,启用好东西并设置值。

例如这样的事情:

#!/bin/bash
HAS_NICE_THINGS=0
NICE_THINGS_VALUE=50
        
while getopts nN: option; do
    case "${option}" in
    n)
        HAS_NICE_THINGS=1
        shift;;
    N)
        HAS_NICE_THINGS=1
        NICE_THINGS_VALUE=$OPTARG
        shift; shift;;
    esac
done

if [ "$HAS_NICE_THINGS" = 1 ]; then
    echo "nice things enabled with value $NICE_THINGS_VALUE"
fi

会给

$ bash nice-getotps.sh -n
nice things enabled with value 50
$ bash nice-getopts.sh -N42
nice things enabled with value 42

util-linuxgetopt使用双冒号语法来获取可选的选项参数。使用起来有点别扭,而且需要处理eval,但如果操作正确,它似乎可以正常工作。

手册页:

-o shortopts[...] 中的每个短选项字符捷径后面可以跟一个冒号以指示它有一个必需参数,并可以跟两个冒号以指示它有一个可选参数。

使用脚本来打印原始值,以便我们可以检查它是否正常工作(getopt-optional.sh):

#!/bin/bash
getopt -T
if [ "$?" -ne 4 ]; then
    echo "wrong version of 'getopt' installed, exiting..." >&2
    exit 1
fi 
params="$(getopt -o an:: -- "$@")"
eval set -- "$params"
while [ "$#" -gt 0 ]; do
    case "$1" in
    -n)
        echo "option -n with arg '$2'"
        shift 2;;
    -a)
        echo "option -a"
        shift;;
    --) 
        shift
        break;;
     *) 
        echo "something else: '$1'"
        shift;;
    esac
done
echo "remaining arguments ($#):"
printf "<%s> " "$@"
echo

我们得到

$ bash getopt-optional.sh -n -a
option -n with arg ''
option -a
remaining arguments (0):
<> 
$ bash getopt-optional.sh -n'blah blah' -a
 -n 'blah blah' -a --
option -n with arg 'blah blah'
option -a
remaining arguments (0):
<> 

没有参数 to-n显示为空参数。

无论如何,您都不能传递显式的空参数,因为选项参数需要与选项本身位于同一命令行参数内,并且与删除引号后的-n相同。-n""这使得可选选项参数难以使用,因为您需要使用-nx,因为-n x将被视为选项-n(没有 opt-arg),后跟常规非选项命令行参数x。这与-n采用强制选项参数时会发生的情况不同。

更多getopt关于这个 Stackoverflow 答案如何解析 Bash 中的命令行参数?


请注意,这似乎仅限于 的特定实现getopt,它恰好在 Linux 系统上很常见,但在其他系统上可能不常见。其他实现getopt甚至可能不支持参数中的空格(程序必须为它们进行 shell 引用)。 “增强型”util-linux 版本可以-T选择测试您是否安装了该特定版本。这里有一些关于限制和警告的讨论getoptgetopt、getopts 或手动解析 - 当我想同时支持短选项和长选项时使用什么?

另外,getopt它不是一个标准工具getopts

答案2

假设我们想要提供一个-a带有可选选项参数的选项。使用选项应该在 shell 中设置 shell 变量,但如果没有给出选项参数,a_opt则应将其设置为值,否则将其设置为通常的值。$a_default$OPTARG

为了确定何时-a不带参数使用 ,我们有两种情况:

  1. 相信用户正在使用的选项参数getopts实际上是其他选项之一,或者
  2. -a选项用在 list 命令行的末尾(getopts在这种情况下通常会生成有关缺少选项的错误)。

第一种情况是通过查看$OPTARG以确定它是否看起来像其他选项之一来处理的。如果是,则使用默认值,否则使用默认$OPTARG值。当使用默认值时,我们需要在 后面的点重新启动选项解析-a,这意味着移走一些参数(OPTIND其中 -1 个),重置OPTIND为 1,然后添加$OPTARG到位置参数列表的前面。

这一切都意味着我们不能使用-a看起来像选项之一的参数。

第二种情况的处理方法是让选项字符串的第一个字符getopts:。这有两个效果:

  1. getopts不再自行发出错误。
  2. 当需要选项参数的选项没有获得选项参数时,该选项的名称将被放置在 中$OPTARG,并将 an:放入与 一起使用的选项变量中getopts

这意味着我们可以:在常用的选项解析case语句中添加一个 case,用于$OPTARG测试a用户是否-a在命令行的最后使用了该语句。

代码:

#!/usr/local/bin/bash

a_default='A default value'
d_opt=false

while getopts :a:b:c:d opt; do
        case $opt in
                a)
                        case $OPTARG in
                                -[a-d-]*)
                                        shift "$(( OPTIND - 1 ))"
                                        OPTIND=1
                                        set -- "$OPTARG" "$@"
                                        a_opt=$a_default
                                        ;;
                                *)
                                        a_opt=$OPTARG
                        esac
                        ;;

                b)      b_opt=$OPTARG ;;
                c)      c_opt=$OPTARG ;;
                d)      d_opt=true ;;

                :)
                        if [ "$OPTARG" = a ]; then
                                a_opt=$a_default
                        else
                                printf '%s: option requires an argument -- %s\n' \
                                        "$0" "$OPTARG" >&2
                                exit 1
                        fi
                        ;;

                *)      printf '%s: generic option parsing error\n' "$0" >&2
                        exit 1
        esac
done

shift "$(( OPTIND - 1 ))"

printf '%s = "%s"\n' \
        'a_opt' "${a_opt-(Not set)}" \
        'b_opt' "${b_opt-(Not set)}" \
        'c_opt' "${c_opt-(Not set)}" \
        'd_opt' "${d_opt-(Not set)}"

if [ "$#" -gt 0 ]; then
        printf 'Extra argument: "%s"\n' "$@"
fi

测试:

a_opt-a如果不带选项参数使用,则获取默认值:

$ ./script -a
a_opt = "A default value"
b_opt = "(Not set)"
c_opt = "(Not set)"
d_opt = "false"

a_opt-a如果不使用则不会获得值:

$ ./script -b bval
a_opt = "(Not set)"
b_opt = "bval"
c_opt = "(Not set)"
d_opt = "false"

我们可以a_opt像往常一样通过提供选项参数来给出一个值-a

$ ./script -da aval -c cval
a_opt = "aval"
b_opt = "(Not set)"
c_opt = "cval"
d_opt = "true"

我们仍然可以使用--来结束选项解析:

$ ./script -d -a -- aval -c cval hello world
a_opt = "A default value"
b_opt = "(Not set)"
c_opt = "(Not set)"
d_opt = "true"
Extra argument: "aval"
Extra argument: "-c"
Extra argument: "cval"
Extra argument: "hello"
Extra argument: "world"

相关内容