getopts 的简单模拟(09.12 Rev)

       鉴于有些老版本的 busybox 可能没带 getopts 或 getopt 工具,为了写个支持选项的通用脚本,写个函数模拟 getopts,相比之前 shell 的选项解析 中的处理方式,这样也许更简单易用

关于 getopts 与 getopt

       处理命令行参数是个相似而又复杂的事,为此,C 提供了 getopt/getopt_long 等函数,C++ 的 boost 提供了 options 库,shell 中处理此事的是 getopts 和 getopt
      getopts 是 Shell 内置(builtin)命令,只支持短选项,而 getopt 属于外部命令,支持长短选项(绕开了 getopts 不符合 Linux 约定的限制),并具有一些 getopts 没有的特性

getopts 的惯用法

格式: getopts  option_string  opt_var  [args...],若无 args,则获取命令行参数
样例:
while getopts ":a:bc" opt  ##  第一个冒号表示忽略错误;字符后的冒号表示该选项带一个参数
do
        case $opt in  ## opt 保存了解析出的选项名
                a ) echo $OPTARG  ## OPTARG 存储选项所带的参数
                    echo $OPTIND;;  ## OPTIND 存储下一个待处理选项在最初列表中的位置
                b ) echo "b $OPTIND";;
                c ) echo "c $OPTIND";;
                ? ) echo "error"
                    exit 1;;
        esac
done
shift $(($OPTIND - 1))  ## 这样 $* 就只保留除去选项的参数

      getopts 使用了两个隐含变量:一个是 OPTARG,用来取当前选项的参数,另外一个是 OPTIND(选项索引),用来取要处理的下一个选项,初始值为 1。这与 libc 中的 getopt 函数实现相近,可参考这篇:getopt 函数分析
      若 options-strings 开始有冒号(忽略错误),则
a. 当指定的选项不存在时,opt_var 设置为 ":",对应的 $OPTARG 为"对应的选项"
b. 指定的选项带参数的而没有提供参数,opt_var 设为"?",对应的 $OPTARG 为"这时候的选项"
可以根据这两个选项指定不同的反馈信息

实现

      这里用全局变量来保存处理状态,_ARGS_ 保存待处理的命令行参数(内部使用),ARGS_保存其中非选项参数,OPT_VAR 保存识别出的选项,OPT_ARG 保存选项所带的参数,若选项不带参则为空。使用时当然自定义变量不能与之重名,否则程序行为将变为非线性。。。

      相比 getopts,有些特性没了,如 silent 模式,但增加了对长选项的简单支持,带参的长选项需写为 --long-opt=opt-arg 的形式;也支持连续选项,即类似 tar 的 -xvf 写法

 1 _ARGS_="$@"
 2 ARGS_=""
 3 OPT_VAR=""
 4 OPT_ARG=""
 5 
 6 ERR_ILLEGAL_OPT=65
 7 ERR_OPT_NEED_VALID_ARG=67
 8 ERR_UNKNOWN_PARA=69
 9 
10 get_opts()
11 {
12    _get_opts "$1" $_ARGS_  ## 转为位置参数
13    return $?
14 }
15 
16 _get_opts()
17 {
18    local opts="$1" && shift  ## 位置参数 1 为 opt-string
19    OPT_VAR="" && OPT_ARG=""  ## 双清
20    local arg
21    for arg in $_ARGS_  ## 寻找待处理选项
22    do
23       case $arg in
24       --* )  ## 1. 长选项
25             local asn_opt="${arg#--}"
26             OPT_VAR="${asn_opt%%=*}"
27             if [ "$OPT_VAR" != "$asn_opt" ]; then
28                 OPT_ARG="${asn_opt#*=}"
29                 [ -z "$OPT_ARG" ] && ErrorX $ERR_OPT_NEED_VALID_ARG  ## 若等号后未带参数,则出错退出
30             fi
31             ;;
32       -*  )  ## 2. 短选项 
33             local the_opt=${arg#-}
34             local o1=$(expr substr "$the_opt" 1 1)
35             [ $(expr index "$opts" "$o1") -eq 0 ] && ErrorX $ERR_ILLEGAL_OPT  ## 若解析出的选项在 opt-string 里没找到
36             OPT_VAR="$o1"
37             
38             if [ $(expr match "$opts" ".*${o1}:") -gt 0 ]; then
39                [ "$o1" != "$the_opt" -o -z "$2" ] && ErrorX $ERR_OPT_NEED_VALID_ARG
40                OPT_ARG=$2
41                shift
42             fi
43             ;;
44       *   )  ## 3. 保存非选项参数(显然不支持带空格的参数)
45             ARGS_="$ARGS_ $arg"
46             ;;
47       esac
48       shift
49       [ -n "$OPT_VAR" ] && break  ## 若找到选项则 break
50    done
51    _ARGS_="$@"  ## 保存剩余待处理参数
52    [ ${#the_opt} -gt 1 ] && _ARGS_="-${the_opt#?} $_ARGS_"  ## 支持连续选项
53    [ -n "$_ARGS_" -o -n "$OPT_VAR" ] && return 0 || return 1  ## 若有待处理的参数或选项,则返回 0
54 }

其中,ErrorX 是错误处理函数,统一处理错误码,“X”为退出意,当然是否退出可自定义

用法

与 getopts 类似,如:

 1 while get_opts "r:w:"
 2 do
 3     case $OPT_VAR in
 4     r  | retry    ) retry=$OPT_ARG;;  ## 重试次数
 5     w | wait    ) interval=$OPT_ARG;;  ## 重试间隔
 6        *        )  ErrorX $ERR_UNKNOWN_PARA;;
 8     esac
 9 done
10 [ -n "$ARGS_" ] && set $ARGS_  ## 将剩下的非选项参数设为位置参数,方便后继使用

命令行可写成这样:

./tool.sh -r 3 -w 30

或长选项形式:

./tool.sh --retry=3 --wait=30

看到这篇文章 命令行参数解析,发觉倒和 python 的选项有点像,不过看上去它一次就解析完了,呵呵

1 opts,args = getopt.getopt(sys.argv[1:],"h:p:",["host=","port="])
2 for opt,arg in opts:
3       if opt in ("-h","--host"):
4           host = arg
5       if opt in ("-p","--port"):
6           port = arg
原文地址:https://www.cnblogs.com/lookbackinside/p/2676198.html