Skip to content

解析命令行的选项和参数

$1 $2 ...被称作位置参数(posotional parameters)。

1. shift

shift [n]
    把从 n+1 ... 的位置参数重命名为 $1 ...
    The  positional  parameters from n+1 ... are renamed to $1 ....
    n 默认是 1

该指令是用于修改位置参数的指向,比如执行一次shift 2就相当于把向后数2个的那个参数变成被$1引用:

这是一个普通的命令:

$0  $1   $2     $3
|   |    |      |
v   v    v      v
ls  -l  /root  /etc

执行 shift 2 之后就变成了

$0              $1
|               |
v               v
ls  -l  /root  /etc

2. getopts

这是 bash 内置的命令,用于解析输入的选项与参数,主要用于处理小的脚本。

getopts optstring name [args]
  • optstring:定义了要解析的选项及有无参数,只能解析短选项。带有参数的选项后面要有一个冒号,在输入选项和参数时要使用空格隔开

  • name:则是一个自定义变量,每次调用时都会把下一个选项放进该变量,并把下一个参数的索引放到环境变量 OPTIND 中,若选项有要求参数,会把参数放到 OPTARG 环境变量中。

  • args:getopts 通常分析位置参数,但如果 args 中提供了参数,getopts 会分析这些参数,而不再去管命令行传入的参数了。意思就是可以在这里写 optstring 中定义的选项直接传递给 getopts 去解析。

getopts 测试脚本getopts-test.sh

#!/bin/sh
# 测试 getops 的使用

echo "original parameters: ($@)"

while getopts 'abc:' opt
do
    echo "process option $opt"
    case $opt in
        a)
            echo "get a, OPRIND=$OPTIND, OPTARG=$OPTARG"
            ;;
        b)
            echo "get b, OPRIND=$OPTIND, OPTARG=$OPTARG"
            ;;
        c)
            echo "get c, OPRIND=$OPTIND, OPTARG=$OPTARG"
            ;;
        *)
            echo "error option $opt, , OPRIND=$OPTIND, OPTARG=$OPTARG"
            exit 1
    esac
done

echo "now OPTIND=$OPTIND"
shift $(expr $OPTIND - 1)
echo "remain parameters: ($@)"

执行结果:

# 正常执行
[sink@dev ~]$ ./getopts-test.sh -a -b -c 23 opq
original parameters: (-a -b -c 23 opq)
process option a
get a, OPRIND=2, OPTARG=
process option b
get b, OPRIND=3, OPTARG=
process option c
get c, OPRIND=5, OPTARG=23
now OPTIND=5
remain parameters: (opq)

# 缺少参数
[sink@dev ~]$ ./getopts-test.sh -c
original parameters: (-c)
./getopts-test.sh: option requires an argument -- c
process option ?
error option ?, , OPRIND=2, OPTARG=

# 未知选项
[sink@dev ~]$ ./getopts-test.sh -d
original parameters: (-d)
./getopts-test.sh: illegal option -- d
process option ?
error option ?, , OPRIND=2, OPTARG=

带有args的getopts-test.sh版本:

……
while getopts 'abc:' opt -b
……

执行结果:

[sink@dev ~]$ ./getopts-test.sh -a -c 2 dsf
original parameters: (-a -c 2 dsf)
process option b
get b, OPRIND=2, OPTARG=
now OPTIND=2
remain parameters: (-c 2 dsf)
# 不论传入的参数为何,都不再解析这些参数
[sink@dev ~]$ ./getopts-test.sh -d dsf
original parameters: (-d dsf)
process option b
get b, OPRIND=2, OPTARG=
now OPTIND=2
remain parameters: (dsf)

3. getopt

getopt optstring parameters
getopt [options] [--] optstring parameters
getopt [options] -o|--options optstring [options] [--] parameters

OPTIONS
    -a, --alternative   允许长选项使用单个 '-' 开头.
    -l, --longoptions longopts  longopts 的格式为"abcd,efg:,hijk::",后跟一个冒号表示需要参数,
                                后跟两个冒号表示参数可选
    -o, --options shortopts     shortopts 的格式为"ab:c::",后跟一个冒号表示需要参数,
                                后跟两个冒号表示参数可选
    -n, --name progname 报告错误时所用的程序名

短选项:

  • 若该选项需要参数,那么既可以直接跟在选项后面,也可以使用空格分隔;
  • 若该选项的参数可选,那么参数就必须直接跟在选项后面;
  • 还可以在单个-后面直接写好几个不能传参的选项。

长选项:

  • 若该选项需要参数,那么既可以使用=分隔,也可以使用空格分隔;
  • 若该选项的参数可选,那么参数就必须使用=分隔;
  • 长选项还可以缩写,只要缩写是明确的。

命令的输出:

  • 如果可选参数的选项没有传参,那么会其参数变成空字符串""
  • 返回的字符串会按照选项 参数 -- 非选项参数的格式。非选项带有的参数会在--后面出现;
  • 好像不能正确解析带有空格的字符串,会将字符串进行拆分

getopt-test.sh

#!/bin/sh
# 测试 getopt 命令

args=$(getopt -n $0 -l "int,char:,string::" -o "ab:c::" -- $@)
if [ $? != 0 ]
then
    exit 1
fi

echo "args=($args)"

# 重新设定位置参数
set -- $args

echo "\$#=$#, \$@=($@)"

while true
do
    case $1 in
        --int|-a)
            echo "get $1"
            shift
            ;;
        --char|-b)
            echo "get $1, value=$2"
            shift 2
            ;;
        --string|-c)
            echo "get $1, value=\"$2\""
            shift 2
            ;;
        --)
            shift
            break
            ;;
        *)
            echo "It's impossible to get here."
            exit 1
    esac
done

echo "\$#=$#, remain args=($@)"

正常使用看看:

[sink@dev ~]$ sh getopt-test.sh -a -c'a f' --s abcd --int 'hi jk'
args=( -a -c 'a' --string '' --int -- 'f' 'abcd' 'hi' 'jk')
$#=11, $@=(-a -c 'a' --string '' --int -- 'f' 'abcd' 'hi' 'jk')
get -a
get -c, value="'a'"
get --string, value="''"
get --int
$#=4, remain args=('f' 'abcd' 'hi' 'jk')

若可选参数的选项使用了错误的使用方法,那么其参数会变成非选项参数:

[sink@dev ~]$ sh getopt-test.sh -a -c dsaf --s abcd --int 'hi jk'
args=( -a -c '' --string '' --int -- 'dsaf' 'abcd' 'hi' 'jk')
$#=11, $@=(-a -c '' --string '' --int -- 'dsaf' 'abcd' 'hi' 'jk')
get -a
get -c, value="''"
get --string, value="''"
get --int
$#=4, remain args=('dsaf' 'abcd' 'hi' 'jk')

4. 总结

getopts 与 getopt 的不同之处在于:

  • getopts
    • bash 内置命令
    • 只能处理短选项
    • 没有可选参数的选项设定
    • 可以直接读取命令行的选项和参数进行处理
  • getopt
    • 一般Linux发行版都会内置的工具
    • 能够同时处理短选项和长选项,并且长选项可缩写
    • 并不是所有的Linux发行版都实现了可选参数的设定
    • 需要先读取命令行的选项和参数,返回一个字符串,然后进行二次设定位置参数
    • 不能正确处理带有空格的参数