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