当你想用一种专业的方式解析命令行参数时,getopts就是要选择的工具。和它的旧版本兄弟命令getopt不同(注意没有s!),getopts是shell内置命令。高级地方表现在
一些解析位置参数的其他方法(不用getopt(s))在这里介绍了: 如何处理位置参数.
注意getopts不能解析GNU风格的长选项(--myoption)或XF86风格的长选项(-myoption)!
需要先了解一下我们这里探讨的事情,所以让我们来看一个范例… 来看一下下面这行命令:
mybackup -x -f /etc/mybackup.conf -r ./foo.txt ./bar.txt
所有这些都叫位置参数,但是你可以把他们分成一些逻辑组:
让你体会一下为什么getopts很有用: 上面的命令可以像这样读取…
mybackup -xrf /etc/mybackup.conf ./foo.txt ./bar.txt
…使用自己的代码去解析很困难。而getopts可以认出所有常见的选项格式。
选项标识可以有大小写,也可以是数字。甚至是其他可识别字符,但是并不推荐这么做(可用性差而且特殊字符可能会出问题)。
一般你需要调用getopts好几次。每次会使用"下一个"位置参数(和一个可能的附加参数),如果解析成功,会给你返回结果。getopts不会改变位置参数的设定 —— 如果你想要shift掉参数,你必须手工处理:
shift $((OPTIND-1))
# now do something with $@
因为getopts在没有要解析的参数剩余时会返回退出状态FALSE,所以可以很容易的在while循环使用:
while getopts ...; do
...
done
getopts将会解析选项和他们可能的参数。遇到第一个非选项的参数时将会停止解析(一个不以连字符(-)开头的字符串,这不是前面任何一个选项的参数)。同样也会在看到--(双连字符)时停止解析,因为这个含义是选项终止。
变量 | 描述 |
---|---|
OPTIND | 保存下一个要解析的参数的指针。这就是getopts如何"记住"自己的状态和回调请求。同样可以用于在getopts处理过之后shift掉位置参数。OPTIND初始为1, 如果你想要再次使用getopts解析任何参数时需要重新设置为1 |
OPTARG | 这个变量设置为被getopts发现的选项的参数。同样包含了未知的选项标记 |
OPTERR | (可选值0或1)表明Bash是否应该显示getopts内置的错误信息。该值在每个shell启动的时候会被初始化为1 - 所以如果你不想看到烦人的信息请务必设置为0! |
getopts同样使用这些变量用于错误报告(they're set to value-combinations which arent possible in normal operation).#括号里面的不会翻译,预留
getopts的基本语法是:
getopts OPTSTRING VARNAME [ARGS...]
解释:
OPTSTRING | 告诉getopts期望哪个选项和期望的参数在哪里(参考下面的) |
---|---|
VARNAME | 告诉getopts哪个shell变量用于选项报告 |
ARG | 告诉getopts将这些解析为附加单词而不是位置参数 |
option-string告诉getopts期望哪个选项,并且哪个选项必须有参数。语法非常简单 – 每个字母就是选项名本身,下面这个范例告诉getopts寻找-f, -A和-x:
getopts fAx VARNAME
当你想让getopts对某个选项期望一个参数时,只是在这个选项标记后面放置一个:(冒号)。如果你想让-A期望一个参数(例如变成 -A SOMETHING),只需要:
getopts fA:x VARNAME
如果option-string首个字母是:(冒号),通常是荒谬的,因为没有任何选项在它之前,这种情况下,getopts会切换到"静默错误报告"模式。在产品脚本中,这通常就是你想要的结果(自行抓取错误信息处理,不要被烦人的信息所干扰)。
getopts工具会默认解析当前shell或函数的位置参数(意味着它解析的是”$@“)。
你可以给出你自己的一组参数解析。当附加参数在VARNAME参数之后给出时,getopts并不试图解析这些位置参数。
用这种方式,你可以按照你喜欢的方式解析任何选项,这里是一个数组的范例:
while getopts :f:h opt "${MY_OWN_SET[@]}"; do
...
done
不带这些附加参数调用getopts的方式等同于显式的使用”$@“调用:
getopts ... "$@"
关于错误报告,getops可以在两种模式下运行:
对于产品脚本我建议使用静默模式,因为这样看起来更专业,你不想看到更多烦人的信息。同样也更容易处理,失败的用例都以更简单的方式显示。
invalid option | VARNAME is set to ? (quersion-mark) and OPTARG is unset |
---|---|
required argument not found | VARNAME is set to ? (quersion-mark), OPTARG is unset and an error message is printed |
invalid option | VARNAME is set to ? (question-mark) and OPTARG is set to the (invalid) option character |
---|---|
required argument not found | VARNAME is set to : (colon) and OPTARG contains the option-character in question |
足够说明一些问题!
让我们先看一个非常简单的实例: 只有一个期望的选项(-a),没有任何参数。同样我们用带:(冒号)的option string为了禁用详尽错误显示:
#!/bin/bash
while getopts ":a" opt; do
case $opt in
a)
echo "-a was triggered!" >&2
;;
\?)
echo "Invalid option: -$OPTARG" >&2
;;
esac
done
我把这些内容放到一个文件go_test.sh,就是你接下来看到的范例中的名字。
来让我们做一些测试:
$ ./go_test.sh
$
什么都没发生?对的,getopts没看到任何合法或非法的选项(前面有短横线的字母),所以不会触发。
$ ./go_test.sh /etc/passwd
$
还是 — 什么都没发生。非常类似的用例: getopts没看到任何合法或非法的选项(前面有短横线的字母),所以不会触发。
传递给你的脚本的参数当然可以用$1 - ${N}获取。
现在让我们触发一下getopts: 提供选项。
首先,来一个非法的:
$ ./go_test.sh -b
Invalid option: -b
$
和预期的一样,getopts不允许这个选项,像上面说的一样: It placed ? into $opt and the invalid option character (b) into $OPTARG. 我们的用例就验证了这一点。
现在,来一个合法的(-a):
$ ./go_test.sh -a
-a was triggered!
$
你看到了,这个探测结果运行的很完美。在我们的用例a选项放在了变量$opt中。
当然在调用的时候可以组合有效和无效的选项:
$ ./go_test.sh -a -x -b -c
-a was triggered!
Invalid option: -x
Invalid option: -b
Invalid option: -c
$
最后,自然也可以多次给出我们的选项:
$ ./go_test.sh -a -a -a -a
-a was triggered!
-a was triggered!
-a was triggered!
-a was triggered!
$
最后一个例子让我们需要考虑到几点:
让我们把上面的例子扩展一下。只有一点点:
#!/bin/bash
while getopts ":a:" opt; do
case $opt in
a)
echo "-a was triggered, Parameter: $OPTARG" >&2
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
:)
echo "Option -$OPTARG requires an argument." >&2
exit 1
;;
esac
done
让我们继续做和最后一个范例同样的测试:
$ ./go_test.sh
$
像上面一样,什么都没发生。就没触发。
$ ./go_test.sh /etc/passwd
$
非常类似的情况: 没有触发。
非法选项:
$ ./go_test.sh -b
Invalid option: -b
$
和期望的一样,像上面一样,getopts不允许这个选项,和程序算法吻合。
合法选项,但是不带强制要求的参数:
$ ./go_test.sh -a
Option -a requires an argument.
$
选项OK,但是丢掉了一个参数。
让我们提供这个参数:
$ ./go_test.sh -a /etc/passwd
-a was triggered, Parameter: /etc/passwd
$