Bash Shell解析命令行参数,你想编写一个简单的脚本,在屏幕上打印出一行连字符,但同时希望将该脚本参数化,以便指定不同的行长度以及除连字符之外的其他字符。其语法类似如下代码。
dashes # 打印出72个连字符
dashes 50 # 打印出50个连字符
dashes -c = 50 # 打印出50个等号
dashes -c x # 打印出72个字符x
有什么易行的方法可以解析这些简单参数?
解决方案
对于正式的脚本编程,应该使用 bash 内建的 getopts。但这里只是想向你展示 case 语句的实践用法,因此,对于这种简单的情况,我们选择用 case 来解析参数。
下例展示了该脚本的开头部分。
#!/usr/bin/env bash
# 实例文件: dashes
#
# dashes - 打印出一行连字符
#
# 选项:#指定字符数量(默认为72个)
# -c X 使用字符X,不再使用默认的连字符
#
LEN=72
CHAR='-'
while (( $# > 0 ))
do
case $1 in
[0-9]*) LEN=$1
;;
-c) shift;
CHAR=${1:--}
;;
*) printf 'usage: %s [-c X] [#]\n' ${0##*/} >&2
exit 2
;;
esac
shift
done
#
# 未完……
讨论
默认字符数量(72)和默认字符(-
)是在脚本起始部分(位于几行注释之后)设置的。while 允许脚本解析多个参数。只要参数数量($#
)不为 0,循环就会持续进行下去。
case 语句匹配 3 个模式。第一个模式 [0-9]*
能够匹配任意数位以及随后任意数量的字符。可以用更复杂的模式仅允许纯数字的出现,但我们假定任何以数位(digit)开头的参数都是数字(number)。如果事实并非如此(例如,用户输入的是 1T4),那么脚本尝试使用 $LEN 时就会出错。我们暂时可以容忍这种情况。
第二个模式是普通的 -c。这其实算不上模式,只是严格的字面匹配。在此分支中,我们用内建命令 shift 丢弃这个参数(因为已经知道这个参数是什么了)并获取下一个参数(现在已变成第一个参数,因此用 $1
来引用它),同时将用户指定的新字符保存起来。在引用 $1
时,我们使用了 :-(${1:-x})
,以便在未指定新字符时设置默认字符。这样一来,如果用户只输入 -c,却未指定新字符,则使用紧随 :- 之后的默认字符。要是写成 ${1:-x}
,则默认字符为 x。脚本中出现的是 ${1:--}
(注意,有两个减号),因此默认字符就是(第二个)减号。
第三个模式 *
可以匹配任意数量的字符,之前未匹配的参数都能够在此得以匹配。可以将其放在 case 语句的最后一个分支中来负责收尾工作,提醒用户发生了错误(出现了未知参数);打印提示信息,告知用户正确的用法。
如果刚开始接触 bash,可能需要向你解释一下 printf 输出的错误信息。该语句分为 4 部分。第一部分很简单,就是命令名 printf。第二部分是 printf 要用到的格式化字符串。为了避免 shell 解释其中的内容,我们在字符串周围加上了单引号。最后一部分(>&2
)告诉 shell 将 printf 的输出重定向到标准错误。因为属于错误信息,所以这种做法没毛病。不少脚本作者并没有将此放在心上,经常忽略错误信息的重定向。我们认为坚持将错误消息重定向到标准错误是一个很好的习惯。
第三部分对 $0
进行字符串操作。这是一种惯用写法,用于将调用命令时的前导路径部分剔除。如果我们只使用 $0
,会发生什么情况呢?以下是调用同一脚本时的两种错误方法。注意错误信息。
$ dashes -g
usage: dashes [-c X] [#]
$ /usr/local/bin/dashes -g
usage: /usr/local/bin/dashes [-c X] [#]
在第二次调用时,我们使用的是完整路径,于是其也出现在了错误信息中。有人会觉得这样很烦。因此,我们剔除了 $0
中的部分内容,只留下脚本的基本名称(类似于使用 basename 命令)。以后无论以何种方式调用脚本,错误信息看起来都是一模一样的。
$ dashes -g
usage: dashes [-c X] [#]
$ /usr/local/bin/dashes -g
usage: dashes [-c X] [#]
相较于硬编码脚本名称或原封不动地使用 $0
,这种方法肯定要多花点时间,但脚本的可移植性会更好,就算重命名脚本,也用不着再修改代码。如果你更喜欢在子 shell 中使用 basename 命令,同样值得一试,多出的那点运行时间不值一提。输出错误信息后,就退出脚本。
我们用 esac 结束了 case 语句,然后通过 shift 丢弃刚刚在 case 语句中匹配到的参数。要是不这么做的话,就会深陷在 while 循环之中,一遍又一遍地解析同一个参数。shift 会使第 2 个参数($2
)成为第 1 个参数($1
),第 3 个参数成为第 2 个参数,以此类推,同时还会将 $#
减 1。经过几次循环后,$#
最终会变成 0(表示此时已经没有任何参数了),循环随之终止。
这里并没有展示连字符(或者其他字符)的实际输出效果,因为我们希望将重点放在 case 语句及其相关处理上。
酷客网相关文章:
评论前必须登录!
注册