本部分内容参考自《Linux命令行与shell脚本编程大全 第3版》。
如果你已经看过前面的内容,应该就见过了一些同时提供了参数和选项的 bash 命令。 选项是跟在单破折线后面的单个字母,它能改变命令的行为。本节将会介绍 3 种在脚本中处理选项的方法。
查找选项
表面上看,命令行选项也没什么特殊的。在命令行上,它们紧跟在脚本名之后,就跟命令行参数一样。实际上,如果愿意,你可以像处理命令行参数一样处理命令行选项。
处理简单选项
上一篇文章中,你看到了如何使用 shift
命令来依次处理脚本程序携带的命令行参数。你也可以用同样的方法来处理命令行选项。
在提取每个单独参数时,用 case
语句来判断某个参数是否为选项。
$ cat test23.sh
#!/bin/bash
while [ -n "$1" ]
do
case "$1" in
-a) echo "Found the -a option" ;;
-b) echo "Found the -b option" ;;
-c) echo "Found the -c option" ;;
*) echo "$1 is not an option" ;;
esac
shift
done
$ ./test23.sh -c -d
Found the -c option
-d is not an option
case
语句会检查每个参数是不是有效选项。如果是的话,就运行对应 case
语句中的命令。
不管选项按什么顺序出现在命令行上,这种方法都适用。
case
语句在命令行参数中找到一个选项,就处理一个选项。如果命令行上还提供了其他参数,你可以在 case
语句的通用情况处理部分中处理。
分离参数和选项
你会经常遇到想在 shell 脚本中同时使用选项和参数的情况。 Linux 中处理这个问题的标准方式是用特殊字符来将二者分开,该字符会告诉脚本何时选项结束以及普通参数何时开始。
对 Linux 来说,这个特殊字符是双破折线(--
)。 shell 会用双破折线来表明选项列表结束。在双破折线之后,脚本就可以放心地将剩下的命令行参数当作参数,而不是选项来处理了。
要检查双破折线,只要在 case
语句中加一项就行了。
$ cat test24.sh
#!/bin/bash
while [ -n "$1" ]
do
case "$1" in
-a) echo "Found the -a option" ;;
-b) echo "Found the -b option";;
-c) echo "Found the -c 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
在遇到双破折线时,脚本用 break
命令来跳出 while
循环。由于过早地跳出了循环,我们需要再加一条 shift
命令来将双破折线移出参数变量。
$ ./test24.sh -a -b -c -- test1 test2 test3
Found the -a option
Found the -b option
Found the -c option
Parameter #1: test1
Parameter #2: test2
Parameter #3: test3
当脚本遇到双破折线时,它会停止处理选项,并将剩下的参数都当作命令行参数。
处理带值的选项
有些选项会带上一个额外的参数值。在这种情况下,命令行看起来像下面这样。
$ ./testing.sh -a test1 -b -c -d test2
当命令行选项要求额外的参数时,脚本必须能检测到并正确处理。下面是如何处理的例子。
$ ./test -a -b value1 -c
-bash: ./test: No such file or directory
[root@zzehost 1223]# ./test24 -a -b value1 -c
-bash: ./test24: No such file or directory
[root@zzehost 1223]# ./test25.sh -a -b value1 -c
Found the -a option
Found the -b option, with parameter value value1
Found the -c option
$ cat test25.sh
#!/bin/bash
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 ;;
-c) echo "Found the -c option";;
--) shift
break ;;
*) echo "$1 is not an option";;
esac
shift
done
[root@zzehost 1223]# ./test25.sh -a -b value1 -c
Found the -a option
Found the -b option, with parameter value value1
Found the -c option
在这个例子中, case
语句定义了三个它要处理的选项。 -b
选项还需要一个额外的参数值。由于要处理的参数是 $1
,额外的参数值就应该位于 $2
(因为所有的参数在处理完之后都会被移出)。只要将参数值从 $2
变量中提取出来就可以了。当然,因为这个选项占用了两个参数位,所以你还需要使用 shift
命令多移动一个位置。
只用这些基本的特性,整个过程就能正常工作,不管按什么顺序放置选项(但要记住包含每个选项相应的选项参数)。
现在 shell 脚本中已经有了处理命令行选项的基本能力,但还有一些限制。比如,如果你想将多个选项放进一个参数中时,它就不能工作了。
$ ./test25.sh -ac
-ac is not an option
在 Linux 中,合并选项是一个很常见的用法,而且如果脚本想要对用户更友好一些,也要给用户提供这种特性。幸好,有另外一种处理选项的方法能够帮忙。
使用getopt命令
getopt
命令是一个在处理命令行选项和参数时非常方便的工具。它能够识别命令行参数,从而在脚本中解析它们时更方便。
命令的格式
getopt
命令可以接受一系列任意形式的命令行选项和参数,并自动将它们转换成适当的格式。它的命令格式如下:
getopt optstring parameters
optstring
是这个过程的关键所在。它定义了命令行有效的选项字母,还定义了哪些选项字母需要参数值。
首先,在 optstring
中列出你要在脚本中用到的每个命令行选项字母。然后,在每个需要参数值的选项字母后加一个冒号。 getopt
命令会基于你定义的 optstring
解析提供的参数。
下面是个 getopt
如何工作的简单例子。
$ getopt ab:cd -a -b test1 -cd test2 test3
-a -b test1 -c -d -- test2 test3
optstring
定义了四个有效选项字母: a
、 b
、 c
和 d
。冒号(:
)被放在了字母 b
后面,因为 b
选项需要一个参数值。当 getopt
命令运行时,它会检查提供的参数列表( -a -b test1 -cd test2 test3
),并基于提供的 optstring
进行解析。注意,它会自动将 -cd
选项分成两个单独的选项,并插入双破折线来分隔行中的额外参数。
如果指定了一个不在 optstring
中的选项,默认情况下, getopt
命令会产生一条错误消息。
$ getopt ab:cd -a -b test1 -cde test2 test3
getopt: invalid option -- 'e'
-a -b test1 -c -d -- test2 test3
如果想忽略这条错误消息,可以在命令后加 -q
选项。
$ getopt -q ab:cd -a -b test1 -cde test2 test3
-a -b 'test1' -c -d -- 'test2' 'test3'
注意, getopt
命令选项必须出现在 optstring
之前。现在应该可以在脚本中使用此命令处理命令行选项了。
在脚本中使用getopt
可以在脚本中使用 getopt
来格式化脚本所携带的任何命令行选项或参数,但用起来略微复杂。
方法是用 getopt
命令生成的格式化后的版本来替换已有的命令行选项和参数。用 set
命令能够做到。
set
命令的选项之一是双破折线(--
),它会将命令行参数替换成 set
命令的命令行值。
然后,该方法会将原始脚本的命令行参数传给 getopt
命令,之后再将 getopt
命令的输出传给 set
命令,用 getopt
格式化后的命令行参数来替换原始的命令行参数,看起来如下所示。
set -- $(getopt -q ab:cd "$@")
现在原始的命令行参数变量的值会被 getopt
命令的输出替换,而 getopt
已经为我们格式化好了命令行参数。
利用该方法,现在就可以写出能帮我们处理命令行参数的脚本。
$ cat test26.sh
#!/bin/bash
set -- $(getopt -q ab:cd "$@")
#
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 ;;
-c) echo "Found the -c 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
现在如果运行带有复杂选项的脚本,就可以看出效果更好了。
$ ./test26.sh -ac
Found the -a option
Found the -c option
当然,之前的功能照样没有问题。
$ ./test26.sh -ac -b bvalue test1 test2 test3
Found the -a option
Found the -c option
Found the -b option, with parameter value 'bvalue'
Parameter #1: 'test1'
Parameter #2: 'test2'
Parameter #3: 'test3'
现在看起来相当不错了。但是,在 getopt
命令中仍然隐藏着一个小问题。看看这个例子。
$ ./test26.sh -ac -b bvalue 'test1 test2' test3
Found the -a option
Found the -c option
Found the -b option, with parameter value 'bvalue'
Parameter #1: 'test1
Parameter #2: test2'
Parameter #3: 'test3'
getopt
命令并不擅长处理带空格和引号的参数值。它会将空格当作参数分隔符,而不是根据双引号将二者当作一个参数。幸而还有另外一个办法能解决这个问题。
使用更高级的getopts
getopts
命令(注意是复数)内建于 bash shell。它跟近亲 getopt
看起来很像,但多了一些扩展功能。
与 getopt
不同,前者将命令行上选项和参数处理后只生成一个输出,而 getopts
命令能够和已有的 shell 参数变量配合默契。
每次调用它时,它一次只处理命令行上检测到的一个参数。处理完所有的参数后,它会退出并返回一个大于 0 的退出状态码。这让它非常适合用于解析命令行所有参数的循环中。
getopts
命令的格式如下:
getopts optstring variable
optstring
值类似于 getopt
命令中的那个。有效的选项字母都会列在 optstring
中,如果选项字母要求有个参数值,就加一个冒号。要去掉错误消息的话,可以在 optstring
之前加一个冒号。 getopts
命令将当前参数保存在命令行中定义的 variable
中。
optstring
值类似于 getopt
命令中的那个。有效的选项字母都会列在 optstring
中,如果选项字母要求有个参数值,就加一个冒号。要去掉错误消息的话,可以在 optstring
之前加一个冒号。 getopts
命令将当前参数保存在命令行中定义的 variable
中。
让我们看个使用 getopts
命令的简单例子。
$ cat test27.sh
#!/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
$ ./test27.sh -ac -b testValue
Found the -a option
Found the -c option
Found the -b option, with value testValue
while
语句定义了 getopts
命令,指明了要查找哪些命令行选项,以及每次迭代中存储它们的变量名(opt
)。
你会注意到在本例中 case
语句的用法有些不同。 getopts
命令解析命令行选项时会移除开头的单破折线,所以在 case
定义中不用单破折线。
getopts
命令有几个好用的功能。对新手来说,可以在参数值中包含空格。
$ ./test27.sh -ac -b 'test1 test2'
Found the -a option
Found the -c option
Found the -b option, with value test1 test2
另一个好用的功能是将选项字母和参数值放在一起使用,而不用加空格。
$ ./test27.sh -acbtestValue
Found the -a option
Found the -c option
Found the -b option, with value testValue
getopts
命令能够从 -b
选项中正确解析出 testValue
值。除此之外, getopts
还能够将命令行上找到的所有未定义的选项统一输出成问号。
$ ./test27.sh -acd
Found the -a option
Found the -c option
Unknown option: ?
optstring
中未定义的选项字母会以问号形式发送给代码。
getopts
命令知道何时停止处理选项,并将参数留给你处理。在 getopts
处理每个选项时,它会将 OPTIND
环境变量值增一。在 getopts
完成处理时,你可以使用 shift
命令和 OPTIND
值来移动参数。
$ cat test28.sh
#!/bin/bash
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 ]
#
echo
count=1
for param in "$@"
do
echo "Parameter $count: $param"
count=$[ $count + 1 ]
done
$ ./test28.sh -abtestValue -c param1 param2 param3
Found the -a option
Found the -b option, with value testValue
Found the -c option
Parameter 1: param1
Parameter 2: param2
Parameter 3: param3
现在你就拥有了一个能在所有 shell 脚本中使用的全功能命令行选项和参数处理工具。
将选项标准化
在创建 shell 脚本时,显然可以控制具体怎么做。你完全可以决定用哪些字母选项以及它们的用法。
但有些字母选项在 Linux 世界里已经拥有了某种程度的标准含义。如果你能在 shell 脚本中支持这些选项,脚本看起来能更友好一些。
下表显示了Linux中用到的一些命令行选项的常用含义。
选项 | 描述 |
---|---|
-a | 显示所有对象 |
-c | 生成一个计数 |
-d | 指定一个目录 |
-e | 扩展一个对象 |
-f | 指定读入数据的文件 |
-h | 显示命令的帮助信息 |
-i | 忽略文本大小写 |
-l | 产生输出的长格式版本 |
-n | 使用非交互模式(批处理) |
-o | 将所有输出重定向到指定的输出文件 |
-q | 以安静模式运行 |
-r | 递归的处理目录和文件 |
-s | 以安静模式运行 |
-v | 生成详细输出 |
-x | 排除某个对象 |
-y | 对所有问题回答 yes |
评论区