1 getopt详解
1.1 getopt --help
用法: getopt optstring parameters getopt [options] [--] optstring parameters getopt [options] -o|--options optstring [options] [--] parameters ? 解析命令选项。 ? 选项: -a, --alternative 允许长选项以单个 - 开头 -l, --longoptions 要识别的长选项 -n, --name 要报告其错误的程序名称 -o, --options 要识别的短选项名称 -q, --quiet 禁止 getopt(3) 的错误报告 -Q, --quiet-output 无常规输出 -s, --shell 设置 的引用习惯 -T, --test getopt(1) 版本测试 -u, --unquoted 不引用输出 ? -h, --help display this help -V, --version display version
可以看到getopt有3种语法用法。
getopt optstring parameters getopt [options] [--] optstring parameters getopt [options] -o|--options optstring [options] [--] parameters
其中,optstring可以理解为“解析规则”,parameters可以理解为“待解析的字符串”,getopt的作用就是,依据“解析规则”对“待解析的字符串”进行解析,将散乱、自由的命令行选项和参数进行解析,得到一个完整的、规范化的参数列表,这样再使用while、case和shift进行处理即可。
1.2 命令行选项tips
本章节(1.2 命令行选项tips)选自(有删减):设计shell脚本选项:getopt
在学习getopt如何使用之前,必须先知道命令行的一些常识。这些,都可以通过getopt来实现,但有些实现起来可能会比较复杂。
1.2.1 区分option、parameter、argument、option argument和non-option parameter
parameter和argument都表示参数,前者通常表示独立性的参数,后者通常表示依赖于其它实体的参数。parameter的含义更广,argument可以看作parameter的一种。
例如,定义函数时function foo(x,y){CODE} ,函数的参数x和y称为parameter。调用函数并传递参数时,foo(arg1,arg2) 中的arg1和arg2都是依赖于函数的,称为argument更合适,当然也可以称为更广泛的parameter。
再例如,一个命令行:
tar -zcf a.tar.gz /etc/pki
粗分的话,-z 、-c 、-f 、a.tar.gz 、/etc/pki 都可以称为parameter。细分的话:
- "-z -c -f"称为选项,即option
- a.tar.gz是选项"-f"的选项参数(传递给选项的参数),依赖于选项,称为argument更合适,更严格的称呼是option argument
- /etc/pki既不属于选项,也不属于某个选项的参数,它称为非选项类型的参数,对应的名称为non-option parameter
本文要介绍的是getopt,所以只考虑命令行参数的情况。
1.2.2 短选项和长选项以及它们的"潜规则"
Linux中绝大多数命令都提供了短选项和长选项。一般来说,短选项是只使用一个"-“开头,选项部分只使用一个字符,长选项是使用两个短横线(即”–")开头的。
例如"-a “是短选项,”--append "是长选项。
一般来说,选项的顺序是无所谓的,但并非绝对如此,有时候某些选项必须放在前面,必须放在某些选项的前面、后面。
一般来说,短选项:
可以通过一个短横线"-"将多个短选项连接在一起,但如果连在一起的短选项有参数的话,则必须作为串联的最后一个字符。
例如"-avz"其实会被解析为"-a -v -z",tar -zcf a.tar.gz 串联了多个短选项,但"-f"选项有参数a.tar.gz,所以它必须作为串联选项的最后一个字符(实测-f可不在最后,可能是命令做了优化)。
短选项的参数可以和选项名称连在一起,也可以是用空白分隔。例如-n 3 和-n3 是等价的,数值3都是"-n"选项的参数值。
如果某个短选项的参数是可选的,那么它的参数必须紧跟在选项名后面,不能使用空格分开。
一般来说,长选项:
可以使用等号或空白连接两种方式提供选项参数。例如--file=FILE 或--file FILE 。 如果某个长选项的参数是可选的,那么它的参数必须使用"="连接。 长选项一般可以缩写,只要不产生歧义即可。 例如,ls命令,以"a"开头的长选项有3个。
$ ls --help | grep -- '--a'
-a, --all do not ignore entries starting with .
-A, --almost-all do not list implied . and ..
--author with -l, print the author of each file
如果想要指定--almost-all ,可以缩写为--alm ;如果想要指定--author ,可以缩写为--au 。如果只缩写为--a ,bash将给出错误提示,长选项出现歧义:
$ ls --a
ls: option '--a' is ambiguous; possibilities: '--all' '--author' '--almost-all'
Try 'ls --help' for more information.
1.2.3 不带参数的选项、可选参数的选项和带参数的选项
有不同类型的命令行选项,这些选项可能不需要参数,也可能参数是可选的,也可能是强制要求参数的。
前面说了,如果某个选项的参数是可选的,那么它的参数必须不能使用空格将参数和选项分开。如果使用空格分隔,则无法判断它的下一个元素是该选项的参数还是非选项类型的参数。
例如,-c 和--config 选项的参数是可选的,要向这两个选项提供参数,必须写成-cFILE 、--config=FILE ,如果写成-c FILE 、--config FILE ,那么命令行将无法判断这个FILE是提供给选项的参数,还是非选项类型的参数。
一般来说,使用可选参数的情况非常少,至少我目前回忆不起来这样的命令(mysql -p选项是一个)。
1.2.4 使用-- 将选项(及它们的选项参数)与非选项类型参数进行分隔
unix的命令行中,总是可以在非选项类型的参数之前加上-- ,表示选项和选项参数到此为止,后面的都是非选项类型的参数。
例如:
$ cat hello.txt
nihao shijie
this is a line contains hello
this is a other sentence.
hello2
--hello
end, thanks.
$ grep -n hello hello.txt
2:this is a line contains hello
4:hello2
5:--hello
$ grep -n --hello hello.txt
grep: unrecognized option '--hello'
用法: grep [选项]... PATTERN [FILE]...
试用'grep --help' 来获得更多信息。
$ grep -n "\-\-hello" hello.txt
5:--hello
$ grep -n -- --hello hello.txt
5:--hello
上述例程就是-- 的作用,可以强制禁止一个命令把-- 后边的内容识别为选项(或选项参数)。当然,也有例外,并不是所有的命令,在设计的时候都支持了-- 的这一功能,如:
$ echo "-n"
$
$ echo -- "-n"
-- -n
1.2.5 命令行参数中的短横线开头的并不一定总是短选项,也可能是负数参数
例如seq命令:
seq -w -5 -1 5
其中-5和-1都是负数非选项类型的参数。
1.2.6 模式化(模块化)类型的选项
很多unix命令都将选项进行模块化设计。例如ip命令,address模式、route模式、link模式等等。
ip addr OPTIONS
ip route OPTIONS
ip link OPTIONS
ip neigh OPTIONS
1.2.7 其他特性的选项
有些命令还有比较个性化的选项。
比如head命令,-n NUM 选项,即可以指定为-3 ,也可以指定为-n 3 或-n3 。
再比如有的命令支持逻辑运算,例如find命令的-a 、-o 选项。
1.3 用法一
getopt optstring parameters 例:
$ getopt ab:cd -a -b arg_b -d pram1 pram2 pram3
-a -b arg_b -d -- pram1 pram2 pram3
$
主要理解 ab:cd 的意义。这里定义的都是短选项,4个字母代表有4个可以解析出来的选项;b后面的冒号表示这个选项需要一个参数,如果不给选项b一个参数,就会报错:
$ getopt ab:cd -a -d pram1 pram2 pram3 -b
getopt: 选项需要一个参数 -- b
-a -d -- pram1 pram2 pram3
如果上述命令改为下图所示,则不会报错,原因是-b 后的-d 作为了-b 这个选项的参数。这就需要后续再shell脚本的-b 分支中,对齐参数的合法性进行简单地判断。
$ getopt ab:cd -a -b -d pram1 pram2 pram3
-a -b -d -- pram1 pram2 pram3
在1.2.4 使用-- 将选项(及它们的选项参数)与非选项类型参数进行分隔中,介绍了-- 分隔选项的机制,也详细讲述了并不是所有的命令(如echo)都支持使用-- 来分隔选项和非选项。如果我们实现一个脚本(一个命令)的时候,如果使用getopt对参数进行处理,那么我们的脚本是支持-- 分隔选项和非选项的。
$ getopt ab:cd -ad value1 -- -b best1 value2 value3
-a -d -- value1 -b best1 value2 value3
1.4 用法二 用法三
1.4.1 用法一的短板
用法一和用法二、三在功能上也是有区别的。用法一输出的参数都是不带引号的。而另外两种格式输出的参数都是带引号的。重要的区别不在引号上,而是这种用法不支持处理带空格和引号的参数值。它会将空格当作参数分隔符,而不是根据双引号将二者当作一个参数。 加入有这样一个脚本: test.sh
#!/bin/sh
while true
do
case "$1" in
-a)
append="$2"
shift 2
;;
--)
shift
break
;;
*)
break
;;
esac
done
for param in "$@"
do
echo ${param}${append}
done
它实现了对输入的参数进行回显的功能,并且当指定-a “string” 时,这个脚本会在将要回显的每个数据后追加上"string"。如:
$ ./test.sh nihao shijie hello world "this is a sentence"
nihao
shijie
hello
world
this is a sentence
$ ./test.sh -a xxxxxxxxxxx nihao shijie hello world "this is a sentence"
nihaoxxxxxxxxxxx
shijiexxxxxxxxxxx
helloxxxxxxxxxxx
worldxxxxxxxxxxx
this is a sentencexxxxxxxxxxx
不过这个脚本有个弊端,它没有参数进行规范化处理(getopt就是用来规范化参数的),导致-a放在命令最后会失去原本想要达到的效果:
$ ./test.sh nihao shijie hello world "this is a sentence" -a xxxxxxxxxxx
nihao
shijie
hello
world
this is a sentence
-a
xxxxxxxxxxx
那我们加上getopt的用法一 test.sh
ARGS=$(getopt a: "$@")
set -- $ARGS
此时测试-a放在命令最后的效果,可以看到即使放到了最后,-a同样达到了预期的效果:
$ ./test.sh nihao shijie hello world -a xxxxxxxxxxx
nihaoxxxxxxxxxxx
shijiexxxxxxxxxxx
helloxxxxxxxxxxx
worldxxxxxxxxxxx
但是此处使用的是getopt的用法一,不能正确处理带空格和引号的字符串,我们期望的打印是"this is a sentencexxxxxxxxxxx ",而不是把一句话拆成4个单词:
$ ./test.sh nihao shijie hello world -a xxxxxxxxxxx "this is a sentence"
nihaoxxxxxxxxxxx
shijiexxxxxxxxxxx
helloxxxxxxxxxxx
worldxxxxxxxxxxx
thisxxxxxxxxxxx
isxxxxxxxxxxx
axxxxxxxxxxx
sentencexxxxxxxxxxx
此时就需要用到用法二或用法三
1.4.2 用法二
getopt [options] [--] optstring parameters
将上边的test.sh的代码,改为使用getopt的用法二对参数进行处理。同时增加一条echo语句打印getopt处理完之后的参数。
ARGS=$(getopt -- a: "$@")
echo $ARGS
set -- $ARGS
测试是否可以正常解析带引号和空格的字符串:
$ ./test.sh nihao shijie hello world -a xxxxxxxxxxx "this is a sentence"
-a 'xxxxxxxxxxx' -- 'nihao' 'shijie' 'hello' 'world' 'this is a sentence'
'nihao''xxxxxxxxxxx'
'shijie''xxxxxxxxxxx'
'hello''xxxxxxxxxxx'
'world''xxxxxxxxxxx'
'this'xxxxxxxxxxx'
is'xxxxxxxxxxx'
a'xxxxxxxxxxx'
sentence''xxxxxxxxxxx'
采用用法二之后,发现并没有解决方法一的短板,且多了好多引号,显得更乱了。从新增的echo $ARGS 打印来看,方法二解析完的参数,是带引号的,并且正确的使用引号圈出了"this is a sentence"。正是这些引号,导致echo的时候引号增多。这是就需要用到eval命令。在set – $ARGS前加上eval :
ARGS=$(getopt -- a: "$@")
echo $ARGS
eval set -- $ARGS
再次测试:
$ ./test.sh nihao shijie hello world -a xxxxxxxxxxx "this is a sentence"
-a 'xxxxxxxxxxx' -- 'nihao' 'shijie' 'hello' 'world' 'this is a sentence'
nihaoxxxxxxxxxxx
shijiexxxxxxxxxxx
helloxxxxxxxxxxx
worldxxxxxxxxxxx
this is a sentencexxxxxxxxxxx
成功符合预期地执行。 关于eval 的介绍,可以参考man手册的eval的example
1.4.3 用法三
getopt [options] -o|--options optstring [options] [--] parameters
用法三和用法二差不多,只是多了一个-o 选项,一般指定长选项时(-l 选项),会配合使用-o (其他的情况我也不是很了解)。-n 选项指定引用的错误程序名。
ARGV=$(getopt -n $(basename $0) -o hd:s: --l help,destination,source -- "$@")
eval set -- "$ARGV"
|