getopt 命令

使用getopt命令,可以解析任何命令行选项和参数,但是用法比较复杂。getopt的命令用法如下:

$ getopt --help


用法:
getopt optstring parameters
getopt [options] [--] optstring parameters
getopt [options] -o|--options optstring [options] [--] parameters 选项:
-a, --alternative 允许长选项以 - 开始
-h, --help 这个简短的用法指南
-l, --longoptions <长选项> 要识别的长选项
-n, --name <程序名> 将错误报告给的程序名
-o, --options <选项字符串> 要识别的短选项
-q, --quiet 禁止 getopt(3) 的错误报告
-Q, --quiet-output 无正常输出
-s, --shell <shell> 设置 shell 引用规则
-T, --test 测试 getopt(1) 版本
-u, --unquoted 不引用输出
-V, --version 输出版本信息 $

用法一共有3种格式,下面都会用到。

在命令行中简单使用

先看第一个最简单的格式:

 getopt optstring parameters

第一部分是命令名。
第二部分optstring(选项字符串),是这个命令解析的格式。
第三部分parameters(getopt命令的参数),就是需要解析的内容。
因此,getopt会按照 optstring 的设置,将 parameters 解析为相应的选项和参数。参考示例来理解:

$ getopt ab:cd -ad value1 -b best1 value2 value3
-a -d -b best1 -- value1 value2 value3
$

主要理解 ab:cd 的意义。
这里定义的都是短选项,4个字母代表有4个选项。b后面的冒号表示这个选项需要一个参数。如果不给选项b一个参数,就会报错:

$ getopt ab:cd -ad value1 -b
getopt:选项需要一个参数 -- b
-a -d -- value1
$

使用双破折线
如果添加了双破折线,那么无轮后面是什么,都会作为参数而不是选项来处理:

$ getopt ab:cd -- -ad value1 -b best1 value2 value3
-- -ad value1 -b best1 value2 value3
$ getopt ab:cd -ad value1 -- -b best1 value2 value3
-a -d -- value1 -b best1 value2 value3
$

这依然是是命令用法的第一种格式,双破折线是parameters内容的一部分。
双破折线出现位置之前的内容按照optstring的设置来解析,之后的内容一律认为是参数。即使有类似选项的内容,被认作为是参数。

参数包含空格的问题
第一种格式和第二、第三种在功能上也是有区别的。这里输出的参数都是不带引号的。而另外两种格式输出的参数都是带引号的。
重要的区别不在引号上,而是这种用法不支持处理带空格和引号的参数值。它会将空格当作参数分隔符,而不是根据双引号将二者当作一个参数。

支持长选项

参考上面的示例,加上长选项的支持。使用长选项的示例如下:

$ getopt -o ab:cd --long arga,argb:,argc,argd -- -ad -b best --argd value1 value2
-a -d -b 'best' --argd -- 'value1' 'value2'
$

这是命令用法的第三种格式。
-o 表示定义短选项
--long 其实是--longoptions,不过省略任意个字母程序都能认识。或者也可以用-l。这个是指定长选项的。所有内容都要连起来,不能有空格。选项之间用逗号隔开。定义完之后,在用双破折号隔开,后面的内容就是parameters。

错误报告引用的程序名

之前已经试过一次解析错误的报告了:

$ getopt ab:cd -ad value1 -b
getopt:选项需要一个参数 -- b
-a -d -- value1
$

这里错误报告的是getopt错误,可以把这个默认的内容替换掉。一般是换成执行的脚本的名字。
这里使用命令用法的第二种格式,把 optstring 和 parameters 都放到双破折线后面:

$ getopt -- ab:cd -ad value1 -b best1
-a -d -b 'best1' -- 'value1'
$

这样在双破折线前面就可加getopt命令的选项,这里要指定-n选项:

$ getopt -n test.sh -- ab:cd -ad value1 -b
test.sh:选项需要一个参数 -- b
-a -d -- 'value1'
$

这里看到包裹错误是,名字已经被替换掉了。
在脚本中,可以使用 $(basename $0) 或者直接用\$0。

禁止错误报告
还有一个-q参数,可以禁止错误报告,解析错误的选项和参数将被丢弃:

$ getopt -n test.sh -q -- ab:cd -ad value1 -b
-a -d -- 'value1'
$

可选参数

还有一种可选参数,使用两个冒号。这个选项可以有一个或零个参数:

$ getopt -o a::bc: -l arga::,argb,argc: -- -a value1 --arga value2
-a '' --arga '' -- 'value1' 'value2'
$ getopt -o a::bc: -l arga::,argb,argc: -- -avalue1 --arga=value2
-a 'value1' --arga 'value2' --
$

第一次执行是传递的参数是错误的。因为是可选参数,参数和值之间不能有空格隔开,否则会有歧义。必须要连在一起才能认为是前一个选项的参数。否则就被认作是独立的参数了。

小结

getopt 命令的选项所指定的选项字符串的规则:

  • 短选项,每一个字符代表一个选项
  • 长选项,每一个字符串代表一个选项,用逗号分隔
  • 选项后跟一个冒号,表示选项需要一个参数
  • 选项后跟两个冒号,表示选项有一个可选参数(一个或零个参数)
  • 可选参数的参数和值之间不能有空格,短选项直接连起来,长选项加等号连起来

在脚本中使用 getopt

现在已经可以用getopt命令,将命令行参数按照规定的格式解析成规整的格式了。并且在解析过程中,还能发现参数格式错误的情况并报告。
接下来就是在脚本中使用经过getopt命令解析后的参数了。

set 命令

要在脚本中使用getopt。首先,要用getopt命令生成格式化后的版本来替换已有的命令行选项和参数。需要用到set命令。
set命令能够处理shell中的各种变量。具体不展开,这里只用了这个命令的一个选项,双破折线(--)。效果是将命令行参数替换成set命令的参数值。
然后,该方法会将原始脚本的命令行参数传给getopt命令执行,之后再将getopt命令的输出传给set命令,用getopt格式化后的命令行参数来替换原始的命令行参数:

set -- $(getopt ab:cd "$@")

现在原始的命令行参数变量的值会被getopt命令的输出替换。而getopt已经为我们格式化好了命令行参数。

直接使用

在之前编写的脚本的基础上,只要在开头加上一行代码,就可以直接使用了:

set -- $(getopt a:b:s:u "$@")

加上这句后,就是让后续的代码处理getopt返回的参数,而不是调用命令时的命令行参数。
验证效果:

$ ./format.sh -u -a after -b befor value1 value2 value3
BEFOR_VALUE1_AFTER
BEFOR_VALUE2_AFTER
BEFOR_VALUE3_AFTER
$ ./format.sh -u -a after -b befor value1 "value2 value3" value4
BEFOR_VALUE1_AFTER
BEFOR_VALUE2_AFTER
BEFOR_VALUE3_AFTER
BEFOR_VALUE4_AFTER
$

第二条命令并不能处理带空格的参数,因为这里使用的是getopt的第一种格式。

使用第二种格式来解析

要处理空格,就需要使用第二种格式(或者第三种),将命令修改为如下:

set -- $(getopt -- a:b:s:u "$@")

简单的在最前面加上双破折线就好了。这条语句是错误的,后面还要修改。

再来验证一下:

$ ./format.sh -u -a after -b befor value1 "value2 value3" value4
'BEFOR'_'VALUE1'_'AFTER'
'BEFOR'_'VALUE2_'AFTER'
'BEFOR'_VALUE3'_'AFTER'
'BEFOR'_'VALUE4'_'AFTER'
$

使用第二、第三种格式,会用引号来限定参数的内容。但是引号干扰了set命令。

使用 eval 命令
这里出现了一个新的问题,不但没有正确的处理空格,输出的内容还有额外的引号。空格的问题先放一放,这里需要用到eval命令来解决新问题。

eval 命令用于将其后的内容作为单个命令读取和执行,这里用于处理getopt命令生成的参数的转义字符。

关于eval命令,还有一种使用的情景。有时候在脚本中拼接出来的字符串即使打印出来看正确。并且直接复制、粘贴在交互界面中也能正确读被当做命令运行。但是却无法在脚本中被执行。这个时候就可以使用eval命令来解决。它能够把字符串当做命令来执行。
在脚本中通过各种引用和判断拼接出一个复杂的命令的时候,有时候就会出现无法执行的情况。这时候就直接赋值、粘贴去交换界面试一下,如果拼接的结果本身没问题,那么加上eval命令后,应该就能用运行。

修改命令如下:

eval set -- $(getopt -- a:b:s:u "$@")

再次验证:

$ ./format.sh -u -a after -b befor value1 "value2 value3" value4
BEFOR_VALUE1_AFTER
BEFOR_VALUE2 VALUE3_AFTER
BEFOR_VALUE4_AFTER
$

第一种格式加上eval命令也是没有问题的,所以可以无脑用上。

解决空格问题

只要能正确的使用getopt的第二种或第三种格式,那么参数包含空格的问题也就解决了。看上一小节。

参数解析错误并退出

执行命令时,使用错误的参数,当前的效果如下:

$ ./format.sh -u -w -a after -b befor value1 "value2 value3" value4
getopt:无效选项 -- w
BEFOR_VALUE1_AFTER
BEFOR_VALUE2 VALUE3_AFTER
BEFOR_VALUE4_AFTER
$

解析发现问题了,并且报告了,但是脚本没有终止,而是继续执行。如果要判断出解析错误,就需要使用\$?参数。然后退出脚本则是用exit命令。
这里直接直接使用\$?并无法获取到参数解析错误的结果。因为此时的结果是set命令(也可能是eval命令)的执行结果,而getopt是再前一条的命令。
解决这个问题,要先把getopt命令执行一遍,进行判断。然后再用set调用一遍,可以直接使用之前执行的结果:

getopt_cmd=$(getopt -n $(basename $) -- a:b:s:u "$@")
[ $? -ne ] && exit
eval set -- "$getopt_cmd"

这里还加上了报告错误时名称的定义。exit退出时也要指定退出状态为非0,因为是运行错误。

验证效果:

$ ./format.sh -v -a after -w -b
format.sh:无效选项 -- v
format.sh:无效选项 -- w
format.sh:选项需要一个参数 -- b
$ echo $? $

现在解析有问题后,就会直接退出。

完整的代码示例

这里加上长选项以及可选参数的功能。
多加了一个参数 -m, --mark 由于指定使用什么连接符:

  • 默认直接连,不使用连接符号
  • 加上选项,默认使用下划线连接
  • 为选项加上参数后,则使用参数来连接

参数比较多,加了 -h, --help 选项打印参数说明。

完整代码如下:

$ cat format.sh
#!/bin/bash
mark="" # 连接符号
prefix="" # 前缀
base="test" # 默认字符串
suffix="" # 后缀
upper=off # 是否大写
# 显示声明一下这是个数组变量,其实没有必要
declare -a names # 需要格式化输出的所有原始字符串
# 打印的帮助信息
help_str="
参数说明:
-h, --help: 打印帮助信息
-m, --mark [连接符]: 使用连接符,默认是下划线(_),可以指定
-a, --after string: 添加后缀
-b, --befor string: 添加前缀
-s, --string string: 指定中间的字符串,默认是“test”
-u, --upper: 全大写输出
"
# 解析命令行参数
getopt_cmd=$(getopt -o m::ha:b:s:u --long mark::,help,after:,befor:,string:,upper -n $(basename $) -- "$@")
[ $? -ne ] && exit
eval set -- "$getopt_cmd"
# 解析选项
while [ -n "$1" ]
do
case "$1" in
-m|--mark)
case "$2" in
"")
mark="_"
shift ;;
*)
mark="$2"
shift ;;
esac
;;
-h|--help)
echo -e "$help_str"
exit ;;
-a|--after)
suffix="$2"
shift ;;
-b|--befor)
prefix="$2"
shift ;;
-s|--string)
base="$2"
shift ;;
-u|--upper)
upper=on ;;
--) shift
break ;;
*) echo "$1 is not an option"
exit ;; # 发现未知参数,直接退出
esac
shift
done
# 解析参数
while [ -n "$1" ]
do
names=("${names[@]}" "$1")
shift
done
names[]=${names[]:-$base}
for name in "${names[@]}"
do
# 添加前缀和后缀
output="${prefix:+${prefix}${mark}}${name}${suffix:+${mark}${suffix}}"
# 判断是否要全大写输出
if [ $upper = on ]
then
output=${output^^}
fi
# 输出结果
echo "$output"
done
$

验证效果:

$ ./format.sh -a after -b befor VALUE1 "VALUE2 VALUE3" VALUE4
beforVALUE1after
beforVALUE2 VALUE3after
beforVALUE4after
$ ./format.sh -a after -b befor --mark
befor_test_after
$ ./format.sh -a after -b befor --mark="||" -u
BEFOR||TEST||AFTER
$ ./format.sh -a after -b befor --mark="||" -u --help 参数说明:
-h, --help: 打印帮助信息
-m, --mark [连接符]: 使用连接符,默认是下划线(_),可以指定
-a, --after string: 添加后缀
-b, --befor string: 添加前缀
-s, --string string: 指定中间的字符串,默认是“test”
-u, --upper: 全大写输出 $

有getopt就够用了。顺便再简单讲下getopts。
getopts功能上差一点,不过封装的更高级,用起来更简单,需要的代码也会少一点。

getopts 命令

getopts是另一个解析命令行参数的工具。它是Bash的内部命令。
它的优势在于:

  • 不需要通过一个外部程序来处理位置参数
  • 可以很容易地设置用来解析的Shell变量
  • getopts 定义在 POSIX 中

不支持长选项:

getopts 不能解析 GUN 风格的长选项(--long),也不能解析 XF86 风格的长选项(-long)

getopt 是将选项和参数处理后只生成一个输出。我们还要用 set 来完成传递的工作。
getopts 能够和已有的shell参数变量配合默契。每次调用时,一次只处理命令行上检测到的一个参数。处理之后,它会退出并返回一个大于0的退出状态码。这样就非常方便的可以在while循环中使用。

基本用法

getopts 会使用到一下3个变量:
OPTIND: 存放下一个要处理的参数的索引。这是 getopts 在调用过程中记住自己状态的方式。
OPTARG: 由 getopts 找到的选项所对应的参数。
OPTERR: 值为0或1。指示Bash是否应该显示由 getopts 产生的错误信息。

getopts 命令的基本语法:

getopts 选项字符串 名称 [参数]

选项字符串(OPTSTRING):getopts 会有哪些选项,哪些是有参数的(选项后有冒号)
名称(VARNAME):getopts 会将找到的选项赋值给这个名称的变量
参数(ARGS):一般情况向缺省,getopts会去解析脚本调用时的所有的参数。如果执行了这个参数,getopts就不解析传递给脚本的参数了,而是解析这里的参数。

getopts 不会移动变量。在处理完所有的选项后,命令就会停止,并将参数留给我们来继续处理。此时可以先用shit命令配合OPTIND的值来移动到第一个参数的位置:

shift $[ $OPTIND - 1 ]

错误报告模式

getopts命令支持两种错误报告模式:

  • 详细错误报告模式
  • 抑制错误报告模式

对于产品中的脚本,推荐使用抑制错误报告模式。

详细错误报告模式
在详细错误报告模式下,如果 getopts 遇到了一个无效的选项,VARNAME 的值会被设置为问号(?),并且变量 OPTARG 不会被设置。如果需要的参数没找到,VARNAME的值也会被设置为问号(?),变量 OPRARG 也不会被设置,并且会打印一个错误信息。

抑制错误报告模式
在抑制错误报告模式下,如果 getopts 遇到一个无效的选项,VARNAME 的值会被设置为问号(?),并且变量 OPTARG 会被设置为选项字符。如果需要的参数没找到,VARNAME的值会被设置为冒号(:),并且变量 OPTARG 中会包含选项字符。
要使用抑制错误报告模式,只需要在调用 getopts 时,设置选项字符串(OPTSTRING)时以冒号开头即可。下面的例子用的就是一直错误报告模式。

示例代码

这里使用抑制错误报告模式,所以需要自己分析并且报告解析错误。都在代码里了:

$ cat say_hello.sh
#!/bin/bash
defaultname="nobody" # 默认的名字
declare -a names # 存放名字的数组
hello="hello" # 打招呼的用语
end="!" # 结束的内容
tittle=off # 是否首字母大写
# 解析选项
while getopts :n:h:e:t opt
do
case "$opt" in
n) defaultname="$OPTARG" ;;
h) hello="$OPTARG" ;;
e) end="$OPTARG" ;;
t) tittle=on ;;
:) # 没有为需要参数的选项指定参数
echo "This option -$OPTARG requires an argument."
exit ;;
?) # 发现了无效的选项
echo "-$OPTARG is not an option"
exit ;;
esac
done
# 解析参数
shift $[ $OPTIND - ] # 移动到第一个参数的位置
# 这次用for循环遍历
for arg in "$@"
do
names=("${names[@]}" "$arg")
done
names[]=${names[]:-$defaultname}
for name in "${names[@]}"
do
[ "$tittle" = on ] && output="${hello^} ${name^} $end" || output="$hello $name $end"
echo "${output}"
done
$

验证执行如下:

$ ./say_hello.sh
hello nobody !
$ ./say_hello.sh -n adam
hello adam !
$ ./say_hello.sh -n adam -h hi -e. -t
Hi Adam .
$ ./say_hello.sh -h hi -e. -t adam bob clark
Hi Adam .
Hi Bob .
Hi Clark .
$ ./say_hello.sh -a -h hi -e. -t adam bob clark
-a is not an option
$ ./say_hello.sh -h
This option -h requires an argument.
$

选项和参数不能混排:

$ ./say_hello.sh adam
hello adam !
$ ./say_hello.sh adam -t
hello adam !
hello -t !
$

支持双破折线:

$ ./say_hello.sh -t adam
Hello Adam !
$ ./say_hello.sh -t -- adam
Hello Adam !
$

比较下来,使用起来会比getopt方便很多,不过功能上也差了很多,可选参数(双冒号::)应该也是不支持的。另外,如果熟悉getopt的话,每一步的操作都是自己的代码控制的。而getopts就简化了很多地方,比如不会调用shift移动变量。

将选项标准化

转自https://blog.51cto.com/steed/2443718?source=dra

仅做个人学习和交流

shell 命令行参数(getopt和getopts)的更多相关文章

  1. Shell 参数(2) --解析命令行参数工具:getopts/getopt

    getopt 与 getopts 都是 Bash 中用来获取与分析命令行参数的工具,常用在 Shell 脚本中被用来分析脚本参数. 两者的比较 (1)getopts 是 Shell 内建命令,geto ...

  2. shell 命令行参数(基本)

    命令行参数 $0 表示程序名.$1 至 \$9则是位置参数.$# 表示参数的个数.$* 将所有参数当做一个整体来引用$@ 把每个参数作为一个字符串返回,可以使用for循环来遍历$? 最近一个执行的命令 ...

  3. Python 处理脚本的命令行参数-getopt

    # -*- coding:utf-8 -*- import sys def test(): """ 参数列表:sys.argv 参数个数:len(sys.argv) 脚本 ...

  4. 如何让python脚本支持命令行参数--getopt和click模块

    一.如何让python脚本支持命令行参数 1.使用click模块 如何使用这个模块,在我前面的博客已经写过了,可参考:https://www.cnblogs.com/Zzbj/p/11309130.h ...

  5. 命令行参数 getopt模块

    getopt中的函数: getopt.getopt(sys.argv[1:], shortopts, longopts=[]) args指的是当前脚本接收的参数,它是一个列表,可以通过sys.argv ...

  6. getopt函数的使用——分析命令行参数

    getopt(分析命令行参数) getopt(分析命令行参数) 短参数的定义 返回值 范例 getopt_long 相关函数表头文件#include<unistd.h> 函数声明int g ...

  7. C语言中使用库函数解析命令行参数

    在编写需要命令行参数的C程序的时候,往往我们需要先解析命令行参数,然后根据这些参数来启动我们的程序. C的库函数中提供了两个函数可以用来帮助我们解析命令行参数:getopt.getopt_long. ...

  8. Linux getopt/getopts解析命令行参数教程

    一.说明 shell中获取参数可以直接使用$1.$2等形式来获取,但这种方式有明显的限制:每个参数的位置是固定的.比如如果在设计上$1是ip地址$2是端口,那在执行时就必须第一个参数是ip第二个参数是 ...

  9. Shell特殊变量:Shell $0, $#, $*, $@, $?, $$和命令行参数

    特殊变量列表 变量 含义 $0 当前脚本的文件名 $n 传递给脚本或函数的参数.n 是一个数字,表示第几个参数.例如,第一个参数是$1,第二个参数是$2. $# 传递给脚本或函数的参数个数. $* 传 ...

随机推荐

  1. 【开发笔记】-通过js控制input禁止输入空格

    <input type="text" id="fname" onkeyup="myFunction(id)"> <scri ...

  2. 浅谈Object.prototype.toString.call()方法

    在JavaScript里使用typeof判断数据类型,只能区分基本类型,即:number.string.undefined.boolean.object.对于null.array.function.o ...

  3. JavaScript 之 节点操作

    一.文档树结构 DOM 可以将任何 HTML 或 XML 描绘成一个由多层节点构成的结构. 节点分为不同的类型,每种类型分别表示文档中不同的信息.每个节点都拥有各自的特点.数据和方法,另外也与其他节点 ...

  4. Charles弱网测试转载

    一.破解版安装 工具好用是好用,但有个蛋疼的地方,非开源,非开源也就算了,还来个试用30分钟,当时的我就中这招了, 试用了之后发现这工具确实好用,对于测试工程师来说兼抓包定位协议类bug.设置网络阀来 ...

  5. django memcached/redis缓存 =====缓存session

    全站使用 例如 博客等缓存,通过中间件实现全站缓存. 加缓存中间件,那么多中间件加在什么位置? 请求时:缓存加在中间件里的最后一个,比如一次经过1.2.3.4中间件,加在4 返回事:缓存加在中间件里的 ...

  6. Linux下 svn相关操作

    Linux下 svn相关操作 一.首先看看svn安装的位置: 命令: find / -name svn /var/svn :表示安装目录 /user/bin/svn :表示命令目录 可以看到的是svn ...

  7. 记录SQLAlchemy的基本使用

    代码 # -*- coding: utf-8 -*- from sqlalchemy import Column, String, create_engine from sqlalchemy.orm ...

  8. 【Spring】@PathVariable 获取带点参数,获取不全

    一.修改前 @GetMapping("/{name:.+}") public String profile(@PathVariable String name, Model mod ...

  9. error: stdio.h: 没有那个文件或目录

    在64位系统中,编写一个C语言程序后,使用gcc进行编译时,出现了如下的错误: test.c:1:19: fatal  error: stdio.h: 没有那个文件或目录 #include <s ...

  10. PHP中的生成器

    python中有的,php一样不落下呀. <?php //生成器 //生成器代理 //生成器返回表达示 function gen1() { yield '1'; yield '2'; yield ...