shell脚本getopt使用详解

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种语法用法。

  1. getopt optstring parameters
  2. getopt [options] [--] optstring parameters
  3. 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-fa.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

#当想检索hello.txt里的--hello字符串时,--hello会被解析为grep命令的选项而报错
#grep -n "--hello" hello.txt 加上双引号也报错
$ grep -n --hello hello.txt
grep: unrecognized option '--hello'
用法: grep [选项]... PATTERN [FILE]...
试用'grep --help' 来获得更多信息。

#一个可行的方法是,加引号并转义
$ grep -n "\-\-hello" hello.txt 
5:--hello

#一个更优雅的方式是使用--将[选项]和 PATTERN [FILE]分隔开
#grep [选项]... PATTERN [FILE]...
$ grep -n -- --hello hello.txt 
5:--hello

上述例程就是--的作用,可以强制禁止一个命令把--后边的内容识别为选项(或选项参数)。当然,也有例外,并不是所有的命令,在设计的时候都支持了--的这一功能,如:

#比如想要使用echo回显字符串"-n"到屏幕
#结果发现什么都没显示,是因为-n是echo的一个选项
$ echo "-n"
$

#那加上--,禁止echo把-n识别为选项
#结果显示,连同--都被打印出来了,即echo命令在实现的时候,并没有将--作为分割选项和非选项的标志。
$ 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

# 将一下代码加到test.sh的while true的前边
# getopt的用法一
ARGS=$(getopt a: "$@")
#set -- $ARGS的作用是用$ARGS里的内容替换当前shell的$1 $2 ...的内容
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处理完之后的参数。

# getopt的用法二
ARGS=$(getopt -- a: "$@")
echo $ARGS
#set -- $ARGS的作用是用$ARGS里的内容替换当前shell的$1 $2 ...的内容
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

# getopt的用法二
ARGS=$(getopt -- a: "$@")
echo $ARGS
#set -- $ARGS的作用是用$ARGS里的内容替换当前shell的$1 $2 ...的内容
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"
  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值