getopt与getopts的用法

前言

先说结论:不推荐使用。更方便的配置选项的方式是使用Python的argparse库,比这种方式要舒适得多。如果你一定要用Shell脚本配置选项,那么请做好受苦的准备——往下看吧。

短选项

使用getopts命令设置脚本的短选项。这是一个 POSIX 标准命令。
getopts的语法为:
getopts "[:]选项列表" 选项名变量 [参数列表]

参数列表不需要明确指定,默认使用$@
选项均为短选项,列表没有分隔符,所有选项全部连在一起写出。如果一个选项后带:符号,则意味着这个选项需要接受参数。
每次执行getopts时,它都会获取命令行中给出的下一个选项及其参数(在本质上,这是通过$OPTIND变量的值分析的),将选项的字符保存在$选项名变量中,将选项或选项的参数的位置保存在$OPTIND变量中(对于有参选项来说,是参数的位置,对于无参选项来说,就是选项本身的位置),将选项的参数值保存在$OPTARG变量中(对于无参选项来说,这个变量总是会被置空)。

如果一个选项是有参选项,那么千万不能在使用时忘记给出参数,否则会导致非常恶性的 BUG(选项会将下一个选项视为参数),只有当有参选项位于末尾时,getopts才会检测到参数缺失的情况

开头的:意味着关闭错误回显,在没有关闭错误回显的情况下,如果用户的行为不合法(位于末尾的缺少参数的选项,或者并不存在于列表中的选项),那么getopts会给出警告,但是这不会影响脚本的执行;如果关闭了错误回显,那么getopts就不会给出警告,而且还会改变某些行为。
无论是否关闭了错误回显,在遇到不存在于列表中的选项时,getopts都会将$选项名变量设为?,并将选项视为无参选项,选项位置保存在$OPTIND变量中,将$OPTARG变量置空;**但是如果关闭了错误回显,getopts还会将选项名赋给$OPTARG**。

再次强调,如果一个选项缺少参数,那么只有在这个选项位于末尾时getopts才能检测到参数缺失的情况。如果没有关闭错误回显,那么对于缺失参数的选项,getopts会将$选项名变量设为?,并将选项位置保存在$OPTIND变量中,将$OPTARG变量置空;如果关闭了错误回显,那么getopts会将$选项名变量设为:,并将选项位置保存在$OPTIND变量中,将$OPTARG变量置空。

getopts已经遍历完命令行中给出的选项时,如果再次执行getopts,那么getopts会将$选项名变量设为?,对$OPTIND变量不进行任何操作(保留最后一个选项或选项参数的位置),将$OPTARG变量置空,然后返回一个非零的值

为了方便使用,getopts段一般放在脚本的开头位置,而且会结合上一条特性,使用while循环和case条件判断简化遍历提取选项的过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
while (getopts "选项列表" OPTNAME)    # 遍历并提取所有的选项
do
case $OPTNAME in
选项1)
echo $OPTARG # 获取了选项1的参数
;;
选项2)
echo $OPTIND # 获取了选项2或选项2参数的位置
;;
?)
echo "Unknown action" # 不存在的选项
exit 3
;;
esac
done

# 循环结束后,$OPTIND等于最后一个选项或选项参数的位置
shift $((OPTIND-1))

shift $((OPTIND-1))在此处的作用是移除所有的选项及其参数,将位置参数重新复位到$1的位置。这一步操作是一定要有的,否则$1指向的是第一个选项,换句话说,第一个选项会被误认为是第一个位置参数。
切记位置参数在使用getopts时只能放在所有的选项和选项参数之后,否则getopts会不起作用(无法设置任何选项)。

长选项

使用getopt命令设置脚本的长选项,注意这不是一个 POSIX 标准命令,它来自于utils-linux软件包。
getopt的语法为:
getopt -o 短选项列表 -l 长选项列表 参数列表
它还支持以下选项:

1
2
3
4
-a  允许长选项使用单横线开头
-n 指定当前程序名称,一般设为$0
-q 禁止错误输出
-Q 禁止普通输出

参数列表必须明确指定,一般使用$@
短选项列表没有分隔符,所有选项全部连在一起写出。长选项列表以,作为分隔符。
如果一个选项后带:符号,表示这个选项是有参选项,需要接受参数;如果一个选项后带::符号,表示这个选项是可选参数选项,参数可以省略,省略的参数会被置空。

getopt会对命令行中给出的选项和参数进行整理,整理成如下形式:
选项与选项参数 -- 位置参数

选项与选项参数按在命令行中给出的选项顺序排列。
排列后,脚本应当先使用set --命令重设位置参数,然后通过$1变量按顺序遍历选项名及其类型。
如果是无参选项,那么获取后应当执行shift偏移一位,将选项移除。如果是有参选项或参数可选选项,那么获取后应当执行shift 2偏移两位,将选项及其参数移除。
最后,在检测到--时,执行shift偏移一位并结束选项遍历。
这个过程通常要用到while循环和case条件判断:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
eval set -- $(getopt -o 短选项列表 -l 长选项列表 "$@")

while true
do
case "$1" in
选项1)
echo "$1" # 获取选项1
shift
;;
选项2)
echo "$2" # 获取选项2的参数
shift 2
;;
--)
shift
break # 结束遍历
;;
esac
done

# 剩下的都是位置参数
echo "$@"

对于getopt来说,位置参数的位置较为松散,可以放在所有的选项之前,也可以放在所有的选项之后。