shell - 参数解析三种方式(手工, getopts, getopt)

# 选项和参数:

  • Argument, Option: 中文对应「选项」,形如 -a, –save 的都是选项;选项可以接收参数(Parameter),也可以不接受参数。其中-a 为短选项, –save 为长选项
  • Flag: 中文对应「标签」,形如 -v(verbose);标签是布尔值,不接受参数。

bash shell 汇总有三种解析参数的方式:

  • 手工处理: 大多数简单的命令
  • getopts: 大多数复杂命令, 不支持长选项
  • getopt: 支持长选项, getopts和getopt功能相似但又不完全相同,其中getopt是独立的可执行文件,而getopts是由Bash内置的
1
$ ./test.sh -v -f -out /test.log --prefix=/home

如果执行是permission denied: test.sh, 运行 chmod +x test.sh 赋予其可执行权限 或是 通过sh/bash 命令运行

# 手工处理

  • $0 : 在用sh 或者 ./执行脚本时,指的是脚本名,用source或.执行时,永运是bash,这也反应了sh 或者 ./执行脚本的原理和source的方式是不同的.
  • $1 : -v,第一个参数.
  • $2 : -f
  • $3 : -out
  • $4 : /test.log
  • 依次类推 $5 $6 …
  • $# : 参数的个数,不包括命令本身,上例中$#为5.
  • $@ : 参数本身的列表,也不包括命令本身,如上例为 -v -f -out /test.log –prefix=/home
  • $* : 参数本身的列表,也不包括命令本身,但"$*" 和"$@"(加引号)并不同,"$*“将所有的参数解释成一个字符串,而”$@“是一个参数数组。如下例所示:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#!/bin/bash

for arg in "$*"
do
    echo $arg
done

for arg in "$@"
do
    echo $arg
done
1
2
3
4
5
6
-v -f -out /test.log --prefix=/home
-v
-f
-out
/test.log
--prefix=/home

也就是说手工处理方式高度依赖命令行中参数的位置, 只适合简单的参数较少的命令, 手工处理方式能满足大多数的简单需求,配合shift使用也能构造出强大的功能

# getopts

先来看看参数传递的典型用法:

  • ./test.sh -a -b -c : 短选项,各选项不需参数
  • ./test.sh -abc : 短选项,和上一种方法的效果一样,只是将所有的选项写在一起。
  • ./test.sh -a args -b -c :短选项,其中-a需要参数,而-b -c不需参数。
  • ./test.sh –a-long=args –b-long :长选项

# getopts 用法

# 变量

  • OPTIND: getopts 在解析传入 Shell 脚本的参数时(也就是 $@),并不会执行 shift 操作,而是通过变量 OPTIND 来记住接下来要解析的参数的位置。
  • OPTARG: getopts 在解析到选项的参数时,就会将参数保存在 OPTARG 变量当中;如果 getopts 遇到不合法的选项,择把选项本身保存在 OPTARG 当中。
1
getopts OPTSTRING VARNAME [ARGS...]
  • OPTSTRING 记录合法的选项列表(以及参数情况)
  • VARNAME 则传入一个 Shell 变量的名字,用于保存 getopts 解析到的选项的名字(而不是参数值,参数值保存在 OPTARG 里)
  • ATGS… 是可选的,默认是 $@,即传入 Shell 脚本的全部参数

通常来说,我们会将 getopts 放在 while 循环的条件判断式中。getopts 在顺利解析到参数的时候,会返回 TRUE;否则返回 FALSE,用以结束循环.

1
2
3
while getopts ...; do
    ...
done

getopts 在两种情况下会停止解析并返回 FALSE:

  • getopts 读入不以 - 开始的字符串;比如: sh test.sh flag
  • getopts 读入连续的两个 - (i.e. –)

# OPTSTRING

通过 OPTSTRING getopts 知道哪些参数是合法的,哪些参数又是需要接受参数的。 OPTSTRING 的格式很简单,就是一个简单的字符串。字符串里,每一个字母(大小写均可,但区分大小写)都是一个选项的名字。

值得一提的是冒号 (:) 在 OPTSTRING 中,冒号有两种含义:

  • 首位的 : 表示「不打印错误信息」;
  • 紧邻字母(选项名字)的 : 表示该选项接收一个参数。

例如:

1
2
  getopts aBcD VARNAME // 该脚本接受四个标签-a, -B, -c, -D, 均不接受参数
  getopts :aB:Cd VARNAME // 该脚本接受两个标签-a, -B, 两个短选项-C, -d

下面是实例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/bash
echo "$@"
while getopts ":a:bc:" opt; do #不打印错误信息, -a -c需要参数 -b 不需要传参  
  case $opt in
    a)
      echo "-a arg:$OPTARG index:$OPTIND" #$OPTIND指的下一个选项的index
      ;;
    b)
      echo "-b arg:$OPTARG index:$OPTIND"
      ;;
    c) 
      echo "-c arg:$OPTARG index:$OPTIND"
      ;;
    :)
      echo "Option -$OPTARG requires an argument." 
      exit 1
      ;;
    ?) #当有不认识的选项的时候arg为?
      echo "Invalid option: -$OPTARG index:$OPTIND"
      ;;
    
  esac
done
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ ./test.sh -a ssss -b ssss -c
  >>
  -a ssss -b ssss -c
  -a arg:ssss index:3
  -b arg: index:4 #-b并不接受参数, 解析到ssss时直接停止解析

$ ./test.sh  -c xxx -b -a ssssss
  >>
  -c xxx -b -a ssssss
  -c arg:xxx index:3
  -b arg: index:4
  -a arg:ssssss index:6

$ ./test.sh  -c -b -a ssssss  // -c 后面没有参数 -b会解析成-c参数
  >>
  -c -b -a ssssss
  -c arg:-b
  -a arg:ssssss
  
$ ./test.sh -a
  >> 
  a
  Option -a requires an argument.

# getopt

getopt较bash内置的getopts更强大,其不仅支持短参-s,还支持–longopt的长参数,甚至支持-longopt的简化参数。相较于getopts ,getopts 不但支持长短选项,其还支持选项和参数放在一起写。

# getopt 用法

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

选项说明: -a:使getopt长参数支持”-“符号打头,必须与-l同时使用 -l:后面接getopt支持长参数列表 -n program:如果getopt处理参数返回错误,会指出是谁处理的这个错误,这个在调用多个脚本时,很有用 -o:后面接短参数列表,这种用法与getopts类似 -u:不给参数列表加引号,默认是加引号的(不使用-u选项),例如在加引号的时候 –longoption “arg1 arg2” ,只会取到"arg1”,而不是完整的"arg1 arg2"

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/bin/bash
# A small example program for using the new getopt(1) program.
# This program will only work with bash(1)
# Note that we use `"$@"' to let each command-line parameter expand to a
# separate word. The quotes around `$@' are essential!
# We need TEMP as the `eval set --' would nuke the return value of getopt.
TEMP=`getopt -o ab:c:: --long a-long,b-long:,c-long:: -n 'example.bash' -- "$@"`
if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
# Note the quotes around `$TEMP': they are essential!
eval set -- "$TEMP"
while true ; do
        case "$1" in
                -a|--a-long) echo "Option a" ; shift ;;
                -b|--b-long) echo "Option b, argument \`$2'" ; shift 2 ;;
                -c|--c-long)
                        # c has an optional argument. As we are in quoted mode,
                        # an empty parameter will be generated if its optional
                        # argument is not found.
                        case "$2" in
                                "") echo "Option c, no argument"; shift 2 ;;
                                *)  echo "Option c, argument \`$2'" ; shift 2 ;;
                        esac ;;
                --) shift ; break ;;
                *) echo "Internal error!" ; exit 1 ;;
        esac
done
echo "Remaining arguments:"
for arg do echo '--> '"\`$arg'" ; done 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#!/bin/bash
ARGS=`getopt -a -o I:D:T:e:k:LMSsth -l instence:,database:,table:,excute:,key:,list,master,slave,status,tableview,help -- "$@"`
function usage() {
    echo  'help'
}
[ $? -ne 0 ] && usage
#set -- "${ARGS}"
eval set -- "${ARGS}"
while true
do
      case "$1" in
      -I|--instence)
              instence="$2"
              shift
              ;;
      -D|--database)
              database="$2"
              shift
              ;;
      -T|--table)
              table="$2"
              shift
              ;;
      -e|--excute)
              excute="yes"
              shift
              ;;
      -k|--key)
              key="$2"
              shift
              ;;
      -L|--list)
              LIST="yes"
              ;;
      -M|--master)
              MASTER="yes"
              ;;
      -S|--slave)
              SLAVE="yes"
              ;;
      -A|--alldb)
              ALLDB="yes"
              ;;
      -s|--status)
              STATUS="yes"
              ;;
      -t|--tableview)
              TABLEVIEW="yes"
              ;;
      -h|--help)
              usage
              ;;
      --)
              shift
              break
              ;;
      esac
shift
done 
echo instence:$instence database:$database table:$table excute:$excute key:$key

# 选项标准化

在创建shell脚本时,尽量保持选项与Linux通用的选项含义相同,Linux通用选项有:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
-a 显示所有对象
-c 生产一个计数
-d 指定一个目录
-e 扩展一个对象
-f 指定读入数据的文件
-h 显示命令的帮助信息
-i 忽略文本大小写
-l 产生输出得长格式文本
-n 使用非交互模式
-o 指定将所有输出重定向到输出文件
-q 以安静模式运行    
-r 递归的处理目录和文件
-s 以安静模式运行    
-v 生成详细输出  
-x 排除某个对象 
-y 对所有问题回答yes
Licensed under CC BY-NC-SA 4.0