命令行工具Bash

使用 getopts 和 getopt 命令处理命令行选项

2021-09-25  本文已影响0人  舌尖上的大胖

一、为什么需要更加复杂的命令行选项处理方式

我们经常会遇到 Shell 脚本中需要获取命令行参数的场景,最简单的方式就是通过 $1$2 ……这样的方式获取。这种方式对于只需要一两个参数的场景简单实用,但对于需要多个参数、需要可选参数等复杂一些的场景会带来一些问题:

举个例子进行说明,假设我们有一个连接主机的脚本,需要传入以下参数:

如果通过 $1$2 的方式来获取参数,前面提出的几个问题就显得比较明显了。

我们更希望通过类似下方式来传参:

$ myscript -u username -p password -v -n 9999 192.168.1.2

这时 getopt / getopts 就就可以大展拳脚了。

二、getoptsgetopt 的区别和应用场景

getoptgetopts 都是 Bash 中用来获取与分析命令行参数的工具,常用在 Shell 脚本中被用来分析脚本参数。

两者的比较:

三、getopts 使用说明

1、举例

根据前面提到的案例:

$ myscript -u username -p password -v -n 9999 192.168.1.2

参数说明:

参数 说明
-u 用户名
-p 密码
-n 端口
-v 显示详情
无名称参数 主机
#!/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))

# 通过第一个无名称参数获取 主机
CONN_HOST="$1"

# 显示获取参数结果
echo 用户名      "$CONN_USERNAME"
echo 密码        "$CONN_PASSWORD"
echo 主机        "$CONN_HOST"
echo 端口        "$CONN_PORT"
echo 显示详情     "$CONN_SHOW_DETAIL"

2、说明

getopts 的工作思路是:

(1)指定命令行中需要解析的参数名称

本例中,:u:p:n:v 就是指定要解析的参数名称。

规则说明:

(2)通过循环逐个读取参数

使用 getopts 解析参数时,按照指定参数列表依次进行解析。如果本次解析符合指定参数规则,包括参数名称、是否需要传值等规则,则返回成功,进行下一次循环继续解析,否则退出循环。

失败规则:

失败后退出循环。

注意!!!不带名称的参数一定要写到最后!否则会被人为是不期待的参数,导致停止解析。

(3)在解析完所有预期的参数之后,对剩余参数进行处理

在解析完所有预期的参数(这时会退出循环)之后,变量 $OPTIND 存储着最后一个解析的参数的 Index,如还有其它参数,则认为是 getopts 期待之外的参数。这时可以配合 shift 清理掉已经解析过的参数,并通过 $1$2 的方式获取获取剩余参数。

shift $((OPTIND-1))

3、getopts 的局限

对于常用的不太复杂的场景,使用 getopts 处理参数基本够用,也更方便,而且是内部命令,不用考虑安装问题,但也有一些局限:

四、getopt 使用说明

1、安装

getoptutil-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

2、介绍

帮助信息如下:

$ 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、举例

根据前面提到的案例,这里增加一个日志级别选项,此选项有默认值,也可以自行指定参数值。

# 短参数格式
$ 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"

4、说明

其实 getopt 只负责做参数的重新整理,并不管提取参数值。它会根据指定的参数列表,把命令行中的选项参数集中放到前面,仅此而已。这样处理之后,再自己通过代码进行解析就比较简单了。所以上面的代码样例,真正涉及 getopt 使用的只有一行,其余的代码都是配合 getopt 重新排列的参数,自行进一步解析而已。

在本例中,选项参数非选项参数没有按顺序排列,所以先告诉 getopt 命令要解析哪些参数:

getopt -o 'u:p:n:vl::' -l 'username:,password:,port:,verbose,log-level::' -- "$@"

参数规则:

五、参考资料

(完)

上一篇 下一篇

猜你喜欢

热点阅读