Bash里使用getopts解析非选项参数

getopts是一个Bash built-in,可以用它来实现与getopt(3)一致的参数解析功能。

注意它和getopt(1)的区别。getopt是一个单独的命令,而getopts是bash内置命令。

关于option与argument的区别,我这里实在给不出准确的定义,只能根据自己的理解试着解释一下。

argument是通常所说的命令行参数,在C语言里就是argv数组,根据参数出现的顺序从argv[1]开始依次递增(argv[0]里是被执行的程序本身的程序名)。在Bash里,$0表示脚本的名称,$1开始往后是各个argument。

option有点混乱。我的理解是:一个Option是以-(hyphen-minus character)或--开头的字符串,它后面有可选的argument,如果有则只有一个。也就是说,一个option由一个或两个argument组成

根据上面的描述命令行参数(Arguments)可以分为3类(抱歉我这里会用比较山寨的方法来描述这三类,如果有对应的标准名称,请留言指出)

  • Option with an argument:例如wget的-O选项,必须要指定一个路径或文件名作为参数
  • Option without an argument:看到网上有的地方也称为switcher,开关选项。例如wget的-v和-q选项,可以使程序输出信息为verbose(详尽)或quiet(安静)
  • Non-option argument:其实根据上面的描述,这个第三类有点尴尬:它不符合我定义的Option。例子就是wget命令指定文件的URL,它的前面并没有对应的-开头的字符串。

getopts的基本用法类似下面的例子:

#!/bin/bash
while getopts :s:h opt
do
    case $opt in
        s)
            echo "-s=$OPTARG"
            ;;
        :)
            echo "-$OPTARG needs an argument"
            ;;
        h)
            echo "-h is set"
            ;;
        *)
            echo "-$opt not recognized"
            ;;
    esac
done

这个例子中,定义了一个optstring:s:h)。其中第一个:字符,打开了getopts的silent模式,可以自行对未识别的选项进行处理而不是让getopts自己报错。随后的s:定义了一个带参数的选项,如果-s使用时没有带上对应的argument则可以在随后的case语句中做相应的报错处理。最后的h定义了一个不带参数的选项。getopts的使用基本就是这样了,更详尽的getopts的optstring的定义和使用请参考bash(1)

上面的例子里我没有给出如何解析非选项参数(Non-option argument)。如果我执行./test.sh -s test_string -h filename,那么仅靠getopts是无法获取到filename这个字符串的。

仔细阅读bash(1)之后就会发现,getopts在访问到第一个non-option argument的时候即会停止解析,并设置$OPTIND为第一个non-option argument在$@中的指针。例如上面这个./test.sh -s test_string -h filename例子,getopts解析完之后会将$OPTIND设置为4。

结合$OPTIND变量和shift这个bash built-in,可以写出一个完整的解析所有参数的脚本。以下是一个例子,已经有恰当的注释信息,应该不需要额外解释了。:

#!/bin/bash
declare -a NOA # Array used to store non-option argument
while [ $# -ne 0 ]
do
    # if OPTIND > $#+1, getopts will not change OPTIND's value,so set it to 0
    OPTIND=0
    while getopts :s: opt
    do
        case $opt in
            s)
            echo "-s=$OPTARG"
            ;;
            \?)
            echo "-$OPTARG not recognized"
            ;;
        esac
    done

    # if getopts find a non-option argument
    # it will stop parsing and return OPTIND
    # as index to the first non-option argument
    #
    # if getopts doesn't find any non-option argument
    # it will set OPTIND=$#+1
    if [ $OPTIND -ne $(($#+1)) ]
    then
        shift $(($OPTIND-1))
        # This is just my favorate way to append element to an array in shell.
        # Feel free to change it
        NOA=(`echo ${NOA[*]}` $1)
        shift
    else
        break; # getopts doesn't find any non-option argument
    fi
done
echo ${NOA[*]}

Comments !

blogroll

social