我正在尝试构建一个接受各种选项的 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: $@"
我是否无法检查 anOPTARG
内部的长度是否为零getopts ... while ... case
?
在getopts
我不想依赖:)
.在while ... case ... esac
?之外执行我的参数验证
然后我可能会得到-d
etc 的参数值,并且不会捕获丢失的选项。
答案1
当您使用以下命令调用第二个脚本(我将其保存为getoptit
)时:
getoptit -d -h
这将打印:
MYSQL_HOST='' MYSQL_USER='' MYSQL_PASS='' BACKUP_DIR='-h' Additionals:
if [ ! "$BACKUP_DIR" ]; then
这样就设置了BACKUP_DIR,如果没有设置则测试一下,所以里面的代码没有被触发也是正常的。
如果您想测试每个选项是否设置一次,则必须在根据 $OPTARG 值进行分配之前执行此操作。在分配之前,您可能还应该检查 $OPTARG 是否以 a 开头'-'
(针对-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
;;
...
答案2
由于其他答案实际上并没有太多回答您的问题:这是预期的行为,并且基于如何POSIX正在被解释:
当选项需要选项参数时, getopts 实用程序应将其放置在 shell 变量 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 指出的那样,你将必须自己检查你的选项参数,如果它们等于有效选项,则抛出错误。
答案3
我会继续使用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
答案4
首先,您应该使用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 知道它只是在查找不是有效标志的参数。