主要内容
选项和参数
有时候,我们会把它们混淆,但严格来说,选项(option)和参数(parameter)并不等同。选项用于设置程序的行为,而参数多数都是程序将要被处理的数据。
在shell编程中,这两个东西是必不可少的,因此,如何处理好它们是关键。
先看一个例子:
1 |
ssh -p 1000 -i identify root@192.168.0.10 |
上面的命令中,-p和-i是选项,它们都有各自的选项值,分别是1000和identify,而root@192.168.0.10则是参数,告诉ssh命令使用root用户登录到远程主机。
在脚本中使用选项和参数
在shell脚本中,无论是选项还是参数,都是通过位置参数的形式存在的。例如:
1 2 3 4 |
#!/bin/bash #打印前四个位置参数的值 echo $1, $2, $3, $4 |
在shell中运行:
1 2 |
[normal@centos7-server tmp]$ ./arg.sh -a -b one two -a, -b, one, two |
通常,选项都是以-开头,后面跟着的就是选项值,下面是是参数遍历的例子:
1 2 3 4 5 6 7 8 9 10 11 |
#!/bin/bash while [ -n "$1" ] do case "$1" in -a) echo "Found the -a option" ;; -b) echo "Found the -b option " ;; *) echo "$1 is not an option" ;; esac shift done |
方法是通过while循环检查位置参数$1的值是否为空,如果不为空则继续遍历,shift的作用是把已经处理的位置参数移除,直到参数栈为空。
然而这种方法存在一个问题,就是不能很好区分选项和参数,参数不多的时候问题不是很明显,但是一旦参数多了,问题处理起来就变得麻烦
区分选项和参数
set方法有一个选项--
(双横线),该选项的作用是把--
后面的所有参数都变为位置参数,我们也可以利用这个原理区分选项和参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#!/bin/bash while [ -n "$1" ] do case "$1" in -a) echo "Found the -a option" ;; -b) echo "Found the -b option" ;; --) shift break;; *) echo "$1 is not an option";; esac shift done # 处理--后面剩下的 count=1 for param in $@ do echo "Parameter #$count: $param" count=$[ $count + 1 ] done |
运行结果:
1 2 3 4 5 |
[normal@centos7-server tmp]$ ./option2.sh -a -b -- one two Found the -a option Found the -b option Parameter #1: one Parameter #2: two |
处理带选项值的选项
像一开头的ssh例子,有两个选项是有值的,因此要处理这些带值的选项。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#!/bin/bash while [ -n "$1" ] do case "$1" in -a) echo "Found the -a option" ;; -b) param=$2; echo "Found the -b option, with param value $param" shift ;; -- ) shift break ;; *) echo "$1 is not an option" ;; esac shift done # do the rest parameter |
运行:
1 2 3 4 5 |
[normal@centos7-server tmp]$ ./option3.sh -a -b test -- one two Found the -a option Found the -b option, with parameter value 'test' Parameter #1: 'one' Parameter #2: 'two' |
注意处理-b选项的时候,因为跟在-b后面的就是选项的值,当前-b的位置是$1,即它的选项值就是$2,所以把这个值保存在变量param中,因为$2已经处理,因此要把它shift掉。
使用getopt优化程序
虽然使用--
可以区分选项和参数,但是我们在使用系统的程序的时候,很少这样传递参数的,--
的出现显得非常奇怪;而且,另一个问题是,上面的脚本不能很好地处理选项的合并写法,例如-ab
这样的形式。幸运的是,系统提供了getopt
程序。
getopt语法:
1 |
getopt optstring parameters |
getopt在处理选项上面有些不一样,它通过冒号(:)指定哪些选项允许带值。例如:
1 2 |
[normal@centos7-server tmp]$ getopt ab: -a -b one two -a -b one -- two |
b后面有一个冒号,说明它是可以带值的选项,而a没有冒号,即我们不能为它传递选项值。getopt返回的是格式化好的字符串,它已经把选项和参数区分开,而且把有值和没有值的选项也已经排列好。
实际上,getopt返回的字符串还不能直接使用,它需要通过set来设置位置参数。
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 |
#!/bin/bash set -- $(getopt -q ab:cd "$@") echo while [ -n "$1" ] do case "$1" in -a) echo "Found the -a option" ;; -b) param="$2" echo "Found the -b option, with parameter value $param" shift ;; --) shift break;; *) echo "$1 is not an option";; esac shift done count=1 for param in $@ do echo "Parameter #$count: $param" count=$[ $count + 1 ] done |
通过$()
捕获getopt的输出,然后使用set --
把这些输出设置为位置参数。后面的照常处理即可。通过getopt的好处是我们在传递参数的时候不需要再使用--
,而且可以使用合并选项的写法。
虽然getopt程序解决了一些问题,但是它本身对于双引号并不是十分友好,例如:
1 2 3 4 5 |
[normal@centos7-server tmp]$ ./option3.sh -a -b "test param" test2 Found the -a option Found the -b option, with parameter value 'test param' is not an option Parameter #1: 'test2' |
我们的本意是,“test param”是完整的一个选项值,但是getopt却不能识别,硬生生地把它们拆散了。
使用getopts
getopts是getopt的增强版,上面的问题getopts都可以解决。在使用方法上面,getopts和getopt也有不同,getopt是一次处理所有的参数,然后返回一个格式化好的字符串,而getopts是每次处理一个遇到的参数,直到没有参数位置。
1 2 3 4 5 6 7 8 9 10 11 |
#!/bin/bash while getopts :ab:c opt do case "$opt" in a) echo "Found the -a option" ;; b) echo "Found the -b option, with value $OPTARG";; c) echo "Found the -c option" ;; *) echo "Unknown option: $opt";; esac done |
注意a前面有一个冒号,这个冒号的作用是抑制错误输出,即当遇到不匹配的选项的时候不提示错误。
在循环中,getopts每次把检测到的选项保存到opt参数中,以便在后面的语句中使用,不匹配的选项在opt中保存为?(问号)。getopts中有两个特殊的预定义变量,一个是OPTARG,另一个是OPTIND(option index)。OPTARG是解析到的选项值,OPTIND是选项的索引,表示下一个要处理的选项索引值。
另外,getopts在处理选项的时候会自动把-(横杠)去掉,因此,我们在编写脚本的时候不需要添加这个横杠。
运行结果:
1 2 3 4 |
[normal@centos7-server tmp]$ ./options.sh -ab test1 -c Found the -a option Found the -b option, with value test1 Found the -c option |
使用getopts处理选项和参数
getopts中不使用--
区分选项和参数,而是配合OPTIND变量来处理的。
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 # Processing options & parameters with getopts # echo while getopts :ab:cd opt do case "$opt" in a) echo "Found the -a option" ;; b) echo "Found the -b option, with value $OPTARG" ;; c) echo "Found the -c option" ;; d) echo "Found the -d option" ;; *) echo "Unknown option: $opt" ;; esac done # shift $[ $OPTIND - 1 ] # count=1 for param in "$@" do echo "Parameter $count: $param" count=$[ $count + 1 ] done |
实际上,OPTIND的长度是optstring(这里是ab:cd)的长度加1,因为OPTIND是下一个要处理的选项索引,因此,当处理完所有的选项后,OPTIND的值就是第一个要处理的参数的位置。所以,上面shift的时候要减去一。
运行结果:
1 2 3 4 5 6 7 |
[normal@centos7-server tmp]$ ./options1.sh -a -b "a test" -c -d test2 test3 Found the -a option Found the -b option, with value a test Found the -c option Found the -d option Parameter 1: test2 Parameter 2: test3 |
可见,getopts可以正确处理双引号中有空格的字符串。
转载请注明:Pure nonsense » shell程序设计(七)选项和参数