Bash Shell正则表达式测试

有时候,即便是 extglob 选项所启用的扩展模式匹配也不够用。你真正需要的是正则表达式。假设你将一张古典音乐 CD 翻录到了目录中,浏览该目录会发现下列文件名:

$ ls
Ludwig Van Beethoven - 01 - Allegro.ogg
Ludwig Van Beethoven - 02 - Adagio un poco mosso.ogg
Ludwig Van Beethoven - 03 - Rondo - Allegro.ogg
Ludwig Van Beethoven - 04 - "Coriolan" Overture, Op. 62.ogg
Ludwig Van Beethoven - 05 - "Leonore" Overture, No. 2 Op. 72.ogg
$

你想写一个脚本,把这些文件名改得简单些,比如只保留曲目编号。该怎么实现呢?

解决方案

使用 =~ 运算符进行正则表达式匹配。只要能够匹配到某个字符串,就可以在 shell 数组变量 $BASH_REMATCH 中找到模式中的各个部分所匹配到的内容。下例是脚本中负责模式匹配的部分。

#!/usr/bin/env bash
# 实例文件:trackmatch
#
for CDTRACK in *
do
    if [[ "$CDTRACK" =~ "([[:alpha:][:blank:]]*)- ([[:digit:]]*) - (.*)$" ]]
    then
        echo Track ${BASH_REMATCH[2]} is ${BASH_REMATCH[3]}
        mv "$CDTRACK" "Track${BASH_REMATCH[2]}"
    fi
done

 这要求使用 bash 3.0 或更高版本,老版中没有 =~ 运算符。另外,bash 3.2 统一了条件运算符 == 和 =~ 中的模式处理,但同时有一处细微的引用 bug,后来在 3.2 的补丁 #3 中得以纠正。如果此处给出的解决方案无效,可能是你使用的 bash 3.2 没有安装这个补丁,需要升级到更高版本。你也可以使用另一个可读性较差的写法来避开这个 bug:删除正则表达式两边的引号,逐个转义其中每个括号和空格字符。但这么一来,整个正则表达式很快就会变得丑陋不堪。
 

if [[ "$CDTRACK" =~ \([[:alpha:][:blank:]]*\)-\ \
> \([[:digit:]]*\)\ -\ \(.*\)\$ ]]

讨论

如果熟悉 sed、awk 以及老版本 shell 中的正则表达式,你也许会注意到它们与示例中的新写法有少许不同。最明显的是 [:alpha:] 这样的字符类以及用于分组的括号不需要转义,我们并没有像在 sed 中那样写成 \(。这里 \( 表示字面意义上的括号。

bash 内建的数组变量 $BASH_REMATCH 的各个元素由括号中的每个子表达式产生。第 0 个元素(${BASH_REMATCH[0]})是正则表达式所匹配的整个字符串。子表达式可以通过 ${BASH_REMATCH[1]}${BASH_REMATCH[2]} 等形式引用。只要将正则表达式写成这样,就会生成变量 $BASH_REMATCH。由于其他 bash 函数也许会使用正则表达式进行匹配,因此你可能需要尽快将该变量另存起来,以备后用。这个示例立刻在 if 的 then 子句中使用了该值,所以就不用再将其保存到别处了。

因为实在是难以解读,所以正则表达式往往被描述为只写表达式。我们通过分步的方法来展示如何从头构建一个完整的正则表达式。示例涉及的文件名的一般结构如下所示:

Ludwig Van Beethoven - 04 - "Coriolan" Overture, Op. 62.ogg

其中包括:作曲人姓名、曲目编号、曲目名称以及结尾的 .ogg(文件保存为 Ogg Vorbis 格式,这种格式体积更小,保真度更高)。

表达式左边是一个开括号(或者左括号)。这表示第一个子表达式开始。我们要在其中匹配文件名的第一部分,也就是作曲人姓名,这部分以粗体标出:

([[:alpha:][:blank:]]*)- ([[:digit:]]*) - (.*)$

作曲人姓名由任意数量的字母和空白字符组成。我们用方括号将组成姓名的各种字符划分在一起。其中没有使用 [a-zA-Z] 的写法,而是采用了 POSIX 字符组 [:alpha:][:blank:],并将它们放入方括号。随后的星号表示 0 次或多次重复。右括号关闭了第一个子表达式,接下来是一个字面连字符和空白字符。

第二个子表达式(标记为粗体)会尝试匹配曲目编号。

([[:alpha:][:blank:]]*)- ([[:digit:]]*) - (.*)$

这个子表达式以另一个左括号起始。曲目编号为整数,由多个数位组成(字符类 [:digit:]),我们将其写入另一对方括号并在后面加上一个星号,也就是 [[:digit:]]*,以此表示方括号中的内容(数位)重复出现 0 次或多次。接着是字面空白字符、连字符、空白字符。

最后一个子表达式匹配包括曲目名、文件扩展名在内的所有剩余内容。

([[:alpha:][:blank:]]*)- ([[:digit:]]*) - (.*)$

括号中的 .* 是一种常见的正则表达式,并不陌生,它表示任意字符(.)出现任意多次(*)。我们用美元符号结束整个表达式,以匹配字符串结尾。匹配过程区分大小写,但是可以用 shopt -s nocasematch(bash 3.1 版本以上可用)修改这种行为。该选项会影响到 case 和 [[ 命令。

酷客网相关文章:

赞(0)

评论 抢沙发

评论前必须登录!