bash getopts,仅短选项,全部需要值,自己进行验证


8

我正在尝试建立一个接受各种选项的shell脚本,这getopts似乎是一个不错的解决方案,因为它可以处理选项和参数的变量顺序(我认为!)。

我只使用简短选项,每个简短选项都需要一个相应的值,例如::./command.sh -a arga -g argg -b argb但我希望允许以非特定顺序输入选项,因为大多数人习惯于使用Shell命令。

另一点是,我希望对case语句参数值进行自己的检查,最好是在语句中进行检查。原因是我:)case陈述中对的测试产生了不一致的结果(可能是由于缺乏理解)。
例如:

#!/bin/bash
OPTIND=1 # Reset if getopts used previously
if (($# == 0)); then
        echo "Usage"
        exit 2
fi
while getopts ":h:u:p:d:" opt; do
        case "$opt" in

                h)
                        MYSQL_HOST=$OPTARG
                        ;;
                u)
                        MYSQL_USER=$OPTARG
                        ;;
                p)
                        MYSQL_PASS=$OPTARG
                        ;;
                d)
                        BACKUP_DIR=$OPTARG
                        ;;
                \?)
                        echo "Invalid option: -$OPTARG" >&2
                        exit 2;;
                :)
                       echo "Option -$OPTARG requires an argument" >&2
                       exit 2;;
        esac
done
shift $((OPTIND-1))
echo "MYSQL_HOST='$MYSQL_HOST'  MYSQL_USER='$MYSQL_USER'  MYSQL_PASS='$MYSQL_PASS'  BACKUP_DIR='$BACKUP_DIR' Additionals: $@"

发生这种情况而失败... ./command.sh -d -h
当我希望它将-d标记为需要参数时,但得到的值-d=-h不是我所需要的。

因此,我认为在case语句中运行自己的验证会更容易,以确保每个选项都只设置一次。

我正在尝试执行以下操作,但未if [ ! "$MYSQL_HOST" ]; then触发我的代码块。

OPTIND=1 # Reset if getopts used previously

if (($# == 0)); then
        echo "Usage"
        exit 2
fi

while getopts ":h:u:p:d:" opt; do
        case "$opt" in

                h)
                        MYSQL_HOST=$OPTARG
                        if [ ! "$MYSQL_HOST" ]; then
                                echo "host not set"
                                exit 2
                        fi
                        ;;
                u)
                        MYSQL_USER=$OPTARG
                        if [ ! "$MYSQL_USER" ]; then
                                echo "username not set"
                                exit 2
                        fi
                        ;;
                p)
                        MYSQL_PASS=$OPTARG
                        if [ ! "$MYSQL_PASS" ]; then
                                echo "password not set"
                                exit 2
                        fi
                        ;;
                d)
                        BACKUP_DIR=$OPTARG
                        if [ ! "$BACKUP_DIR" ]; then
                                echo "backup dir not set"
                                exit 2
                        fi
                        ;;
                \?)
                        echo "Invalid option: -$OPTARG" >&2
                        exit 2;;
                #:)
                #       echo "Option -$opt requires an argument" >&2
                #       exit 2;;
        esac
done
shift $((OPTIND-1))

echo "MYSQL_HOST='$MYSQL_HOST'  MYSQL_USER='$MYSQL_USER'  MYSQL_PASS='$MYSQL_PASS'  BACKUP_DIR='$BACKUP_DIR' Additionals: $@"

是否有原因我无法检查内部是否OPTARG具有零长度getopts ... while ... case

有什么更好的方式来与经营自己的参数验证getopts的情况下,我不希望被依靠:)。在while ... case ... esac?之外执行我的参数验证。
然后,我可能最终得到-detc的参数值,而没有捕获到丢失的选项。

Answers:


4

当您使用以下命令调用第二个脚本时(我将其另存为getoptit):

getoptit -d -h

这将打印:

MYSQL_HOST=''  MYSQL_USER=''  MYSQL_PASS=''  BACKUP_DIR='-h' Additionals: 

因此已设置BACKUP_DIR,并且正在测试if [ ! "$BACKUP_DIR" ]; then是否未设置BACKUP_DIR ,因此正常情况下不会触发其中的代码。

如果要测试每个选项是否设置一次,则必须先执行此操作,然后再根据$ OPTARG值进行设置。在分配之前,您可能还应该检查$ OPTARG是否以'-'(表示-d -h错误)开头:

...
            d)
                    if [ ! -z "$BACKUP_DIR" ]; then
                            echo "backup dir already set"
                            exit 2
                    fi
                    if [ z"${OPTARG:0:1}" == "z-" ]; then
                            echo "backup dir starts with option string"
                            exit 2
                    fi
                    BACKUP_DIR=$OPTARG
                    ;;
...

很好的解释。一旦我想到了实际上while在选项上运行了一个循环这一事实,便说得通。这是我使用的方法,尽管与其他方法相比比较冗长,但很清楚脚本中正在发生什么。在这种情况下,其他人的可维护性很重要。
batfastad

3

由于其他答案实际上并不能回答您这么多问题:这是预期的行为,并且取决于POSIX的解释方式:

当选项需要选项参数时,getopts实用程序应将其放在外壳变量OPTARG中。如果未找到任何选项,或者找到的选项没有选项参数,则应将OPTARG取消。)

这就是所有要说明的内容,并且简短getopts地讲了一个(很短的故事):并不神奇地知道,对于需要具有参数的选项,它看到的完全有效的参数实际上不是选项参数,而是另一个选项。没有什么可以阻止您-h-d选项作为有效参数使用,这实际上意味着getopts仅当您需要参数的选项在命令行中位于最后时才会抛出错误,例如:

test.sh

#!/bin/sh

while getopts ":xy:" o; do
    case "${o}" in
        :) echo "${OPTARG} requires an argument"; exit 1;
    esac
done

例:

$ ./test.sh -y -x
$

$ ./test.sh -x -y
y requires an argument

您的第二种方法不起作用的原因是因为进行的解析getopts仍然相同,因此一旦进入循环,“损坏”就已经完成。

如Benubird所指出,如果您绝对要禁止这样做,则必须自己检查选项参数,如果它们等于有效选项,则将引发错误。


2

我会继续使用,getopts但之后再做一些额外的检查:(未测试)

errs=0
declare -A option=(
    [MYSQL_HOST]="-h"
    [MYSQL_USER]="-u"
    [MYSQL_PASS]="-p"
    [BACKUP_DIR]="-d" 
)
for var in "${!option[@]}"; do
    if [[ -z "${!var}" ]]; then
        echo "error: specify a value for $var with ${option[var]}"
        ((errs++))
    fi
done
((errs > 0)) && exit 1

需要bash版本4


1

Gnu非Shell内置的getopt对您的工作量较小,但在您确定发现标志后将要发生的情况(包括自己移去参数)时可以提供更大的灵活性。

我发现比较getopts和getopt的文章可能会有所帮助,因为该手册有点难以理解(至少在喝咖啡之前)。


1

首先,您应该使用if [ -z "$MYSQL_USER" ]

其次,不需要分配- if [ -z "$OPTARG" ]会很好地工作。

第三,我怀疑您真正想要的是什么if [ ${#OPTARG} = 0 ]。$ {#x}是一个bash东西,返回字符串$ x的长度(请参阅此处的更多内容)。

第四,如果您要进行自己的验证,我建议使用getopt而不是getopts,因为它提供了更多的灵活性。

最后,要回答第一个问题,可以通过在顶部放置标志列表来检测何时将标志作为参数传递:

args=( -h -u -p -d )

然后在您要检查提供的参数是否为选项的选项中进行检查,如下所示:

d)
    BACKUP_DIR=$OPTARG
    if [ "$(echo ${args[@]/$OPTARG/})" = "$(echo ${args[@]})" ]
    then
        echo "Argument is an option! Error!"
    fi

这不是一个完美的答案,但可以!您的问题是“ -h”是一个完全有效的参数,并且您无法让shell知道它只是在查找也不是有效标志的参数。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.