Bash Shell解析命令行参数

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 语句及其相关处理上。

酷客网相关文章:

赞(0)

评论 抢沙发

评论前必须登录!