getopt
与 getopts
都是 Bash 中用来获取与分析命令行参数的工具,常用在 Shell 脚本中被用来分析脚本参数。
getopts
是 Shell 内建命令,getopt
是一个独立外部工具getopts
使用语法简单,getopt
使用语法较复杂getopts
不支持长参数(如:--option
),getopt
支持长参数getopts
出现的目的是为了在不太复杂的场景代替getopt
较快捷地执行参数分析工作getopts
负责参数解析,可以方便地提取参数值,getopt
只负责按规则重新对参数进行排列,进一步解析需要自行编写代码处理
当脚本需要传入多个变量时,使用普通的$1,$2,来传递变量,就显得有点笨了(需要记住变量顺序,还要记得默认的可选参数)
我们更希望通过类似下方式来传参:
myscript -u username -p password -v -n 9999 192.168.1.2
这时 getopt
/ getopts
就就可以大展拳脚了。
根据前面提到的案例:
myscript -u username -p password -v -n 9999 192.168.1.2
参数 | 说明 |
---|---|
-u | 用户名 |
-p | 密码 |
-n | 端口 |
-v | 显示详情 |
无名称参数 | 主机IP |
#!/bin/bash
# 处理脚本参数
# -u 用户名
# -p 密码
# -v 是否显示详情
# -n 端口
while getopts ":u:p:n:v" opt_name # 通过循环,使用 getopts,按照指定参数列表进行解析,参数名存入 opt_name
do
case "$opt_name" in # 根据参数名判断处理分支
'u') # -u
CONN_USERNAME="$OPTARG" # 从 $OPTARG 中获取参数值
;;
'p') # -p
CONN_PASSWORD="$OPTARG"
;;
'v') # -v
CONN_SHOW_DETAIL=true
;;
'n') # -n
CONN_PORT="$OPTARG"
;;
?) # 其它未指定名称参数
echo "Unknown argument(s)."
exit 2
;;
esac
done
# 删除已解析的参数
shift $((OPTIND-1))
#也可以将shift写进每个option里面,shift命令用于对参数的移动(左移),通常用于在不知道传入参数个数的情况下依次遍历每个参数然后进行相应处理(常见于Linux中各种程序的启动脚本)。
#shift(shift 1) 命令每执行一次,变量的个数($#)减一(之前的$1变量被销毁,之后的$2就变成了$1),而变量值提前一位。同理,shift n后,前n位参数都会被销毁
# 通过第一个无名称参数获取 主机
CONN_HOST="$1"
# 显示获取参数结果
echo 用户名 "$CONN_USERNAME"
echo 密码 "$CONN_PASSWORD"
echo 主机 "$CONN_HOST"
echo 端口 "$CONN_PORT"
echo 显示详情 "$CONN_SHOW_DETAIL"
:u:p:n:v
就是指定要解析的参数名称。
规则说明
- 其中的字母表示需要解析的参数名称
- 字母后面的冒号
:
表示该参数除了其本身,还会带上一个参数作为选项的值,传入的值通过$OPTARG
变量获取 - 字母后面没有冒号
:
表示该参数为开关型选项,不需要再指定值,只作为是否存在的标记 - 字符串开头的冒号
:
表示解析过程中,遇到未在getopts
参数列表中指定的参数,不显示报错信息。否则会报出错误。
使用 getopts
解析参数时,按照指定参数列表依次进行解析。如果本次解析符合指定参数规则,包括参数名称、是否需要传值等规则,则返回成功,进行下一次循环继续解析,否则退出循环。
失败规则
- 遇到未定义的变量
- 遇到了意外的值,如:在不需要传值的参数后面指定了参数,或者传入了比期待更多的值
失败后退出循环。
注意!!!不带名称的参数一定要写到最后!否则会被认为是不期待的参数,导致停止解析。
getopts
的局限
对于常用的不太复杂的场景,使用 getopts
处理参数基本够用,也更方便,而且是内部命令,不用考虑安装问题,但也有一些局限:
- 选项参数的格式必须是
-d val
而不能是中间没有空格的-dval
- 所有
选项参数
必须写在其它参数的前面,因为getopts
是从命令行前面开始处理,遇到非-
开头的参数,或者选项参数结束标记--
就中止了,如果中间遇到非选项命令行参数
,后面的选项参数就都取不到了。 - 不支持
--debug
这样的长选项
getopt
使用说明
getopt
是 util-linux 包中的一个命令,Linux 中基本都已预安装了getopt
,样例脚本一般安装到如下位置:
/usr/share/doc/util-linux-2.23.2
/usr/share/getopt/
/usr/share/docs/
本样例参考了如下脚本:
/usr/share/doc/util-linux-2.23.2/getopt-parse.bash
macOS 自带的 getopt
功能比较弱,不支持长选项,可以安装 GNU 版本 gnu-getopt
:
brew install gnu-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 输出版本信息
根据前面提到的案例,这里增加一个日志级别
选项,此选项有默认值,也可以自行指定参数值。
# 短参数格式
$ myscript -u username -p password -v -n 9999 192.168.1.2 -l3
# 或 长参数格式
$ myscript --username username --password password --verbose --port 9999 192.168.1.2 --log-level=3
参数说明
参数 | 说明 |
---|---|
-u , --username | 用户名 |
-p , --password | 密码 |
-n , --port | 端口 |
-v , --verbose | 显示详情 |
-l , --log-level | 日志级别,默认级别为 1 |
无名称参数 | 主机 |
#!/bin/bash
# 使用 "$@" 来让每个命令行参数扩展为一个单独的单词。 "$@" 周围的引号是必不可少的!
# 使用 getopt 整理参数
ARGS=$(getopt -o 'u:p:n:vl::' -l 'username:,password:,port:,verbose,log-level::' -- "$@")
if [ $? != 0 ] ; then echo "Parse error! Terminating..." >&2 ; exit 1 ; fi
# 将参数设置为 getopt 整理后的参数
# $ARGS 需要用引号包围
eval set -- "$ARGS"
# 循环解析参数
while true ; do
# 从第一个参数开始解析
case "$1" in
# 用户名,需要带参数值,所以通过 $2 取得参数值,获取后通过 shift 清理已获取的参数
-u|--username) CONN_USERNAME="$2" ; shift 2 ;;
# 密码,获取规则同上
-p|--password) CONN_PASSWORD="$2" ; shift 2 ;;
# 端口,获取规则同上
-n|--port) CONN_PORT="$2" ; shift 2 ;;
# 是否显示详情,开关型参数,带上该选项则执行此分支
-v|--verbose) CONN_SHOW_DETAIL=true ; shift ;;
# 日志级别,默认值参数
# 短格式:-l3
# 长格式:--log-level=3
-l|--log-level)
# 如指定了参数项,未指定参数值,则默认得到空字符串,可以根据此规则使用默认值
# 如果指定了参数值,则使用参数值
case "$2" in
"") CONN_LOG_LEVEL=1 ; shift 2 ;;
*) CONN_LOG_LEVEL="$2" ; shift 2 ;;
esac ;;
--) shift ; break ;;
*) echo "Internal error!" ; exit 1 ;;
esac
done
# 通过第一个无名称参数获取 主机
CONN_HOST="$1"
# 显示获取参数结果
echo '用户名: ' "$CONN_USERNAME"
echo '密码: ' "$CONN_PASSWORD"
echo '主机: ' "$CONN_HOST"
echo '端口: ' "$CONN_PORT"
echo '显示详情: ' "$CONN_SHOW_DETAIL"
echo '日志级别: ' "$CONN_LOG_LEVEL"
总结
其实 getopt
只负责做参数的重新整理,并不管提取参数值。它会根据指定的参数列表,把命令行中的选项参数集中放到前面,仅此而已。这样处理之后,再自己通过代码进行解析就比较简单了。所以上面的代码样例,真正涉及 getopt 使用的只有一行,其余的代码都是配合 getopt 重新排列的参数,自行进一步解析而已。
在本例中,选项参数
与非选项参数
没有按顺序排列,所以先告诉 getopt
命令要解析哪些参数:
getopt -o 'u:p:n:vl::' -l 'username:,password:,port:,verbose,log-level::' -- "$@"
参数规则
-o
参数指定端参数格式,-l
参数指定对应的长参数- 冒号
:
规则与getopts
的规则基本一致。区别在于后面带有两个冒号::
的表示默认值参数 - 对于
默认值选项
,短参数形式参数名与值之间不能有空格,长参数形式参数名与值需要用等号.=
连接