有时候,即便是 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 和 [[
命令。
酷客网相关文章:
评论前必须登录!
注册