了解正则表达式

在 UNIX 系统上构建和测试正则表达式的重要帮助

可以通过多种方式在 UNIX® 系统上构建和测试正则表达式 (regexp)。通过本文,您可以了解各种可用工具和技术,以帮助了解如何为各种程序和语言构造正则表达式。

Michael Stutz (stutz@dsl.org), 作家, 顾问

   Michael Stutz is author of The Linux Cookbook , which he also designed and typeset using only open source software. His research interests include digital publishing and the future of the book. He has used various UNIX operating systems for 20 years. You can reach Michael at stutz@dsl.org.Michael Stutz 是 The Linux Cookbook 一书的作者,他仅使用开放源码软件对该书进行了设计和排版。他的研究兴趣包括数字出版和图书的发展未来。他使用各种 UNIX 操作系统已有 20 多年。您可以通过 stutz@dsl.org 与他联系。



2007 年 8 月 30 日

正则表达式(regular expression,regexp)是用于描述字符串集的匹配模式的概念,在很多程序和语言中都是通用的。这些不同的 regexp 实现在其细节方面存在一些差别,但构建 regexp 的原则全部都是通用的。

本文将介绍一些有用的工具和技术,以帮助了解如何在一系列 UNIX® 应用程序上构建和优化 regexp,包括:

在上下文中突出显示匹配项

构建 regexp 时,这样可帮助在数据集上下文中查看模式与哪些字符串匹配。以清单 1 的四行输入文本和匹配双字符模式的小型 regexp t[a-z] 为例。

清单 1. 包含四行示例文本和对其进行匹配的 regexp
$ cat midsummer
I know a bank where the wild thyme blows,
Where oxlips and the nodding violet grows,
Quite over-canopied with luscious woodbine,
With sweet musk-roses and with eglantine. 
$ grep t[a-z] midsummer
I know a bank where the wild thyme blows,
Where oxlips and the nodding violet grows,
Quite over-canopied with luscious woodbine,
With sweet musk-roses and with eglantine. 
$

由于会在每行至少找到此双字符模式的一个匹配项,因此 grep 命令会输出输入文件中的每个行。但具体 是这些行中的哪些字符与 regexp 匹配呢?

通过这样的简单 regexp,就可以轻松地自动将其标识出来。但在构建复杂的 regexp 而且具有大型数据集或输入文件时,要知道 regexp 可能匹配哪些字符串可能就会困难得多。能够准确地知道每行上匹配哪些文本将非常有用。在上下文中查看您的 regrxp 的一个方法就是在输出中对其进行标识。

可以使用多个应用程序完成此工作,包括 grepsed 和 Emacs。

使用 grep 进行突出显示

一些较新版本的 grep(如 GNU grep)可在使用 --color 选项时用颜色突出显示 regexp,如图 1 中所示。

图 1. grep 中以颜色加以标识的匹配字符串
grep 中以颜色加以标识的匹配字符串

如果您的终端支持彩色显示,则这样可非常方便地查看 regexp 具体匹配了哪些字符串。

使用 sed 进行突出显示

还可以在 sed(流编辑器,stream editor)中进行 regexp 突出显示。sed 命令:

's/regexp/[&]/g'

将输出输入内容的副本,并标明括号中包含的 regexp 的所有实例。清单 2 显示了对于示例文本的输出。

清单 2. sed 中标记的匹配字符串
$ sed 's/t[a-z]/[&]/g' midsummer
I know a bank where [th]e wild [th]yme blows,
Where oxlips and [th]e nodding violet grows,
Qui[te] over-canopied wi[th] luscious woodbine,
Wi[th] sweet musk-roses and wi[th] eglan[ti]ne. 
$

还可以采用其他方式标记 regexp。如果输入的是 Groff 文档,可以对 regexp 加粗并将文档发送给 groff 进行处理:

$ sed 's/t[a-z]/\\fB&\\fP/g' infile.roff | groff -

还可以编写简短的 sed 程序来以颜色标记输出内容。如果您的外壳支持转义序列,则可以在文件的上下文中突出显示所有 regexp。由于输入转义序列非常麻烦,因此无疑会希望从脚本运行,如清单 3 中所示。

清单 3. 以颜色突出显示匹配模式的 sed 脚本
#!/bin/sh
# highlights regexp pattern in input file
# usage: hre regexp file
sed 's/'$1'/^[[34m&^[[37m/g' < $2

清单中出现两次的 ^[ 是原义转义字符,因此必须使用支持输入原义字符的编辑器(如 Emacs——要在其中键入 C-q ESC 进行输入)输入此清单。3437 是分别用于指定蓝色和白色的 Bash 转义代码。

为了将脚本处理为可执行文件,请键入:

$ chmod 744 hre

然后运行它,如图 2 中所示。

图 2. sed 中以颜色加以标识的匹配字符串
sed 中以颜色加以标识的匹配字符串

虽然可以使用此方法指定突出显示颜色和普通颜色,但有一些事项需要加以注意。例如,清单 3 中所示的脚本仅在终端的普通文本为白色时起作用,因为会将文本还原为这个颜色。如果终端使用其他颜色显示普通文本,则需要更改脚本中的转义代码。(例如,30 表示黑色。)

使用 Emacs 进行突出显示

在 GNU Emacs 编辑器的新版本中,isearch-forward-regexpisearch-backward-regexp 功能可突出显示缓冲区中的所有匹配项。如果在系统上安装了最近的 Emacs 版本,现在就可以尝试一下:

  1. 键入以下命令,以启动 Emacs:
    $ emacs midsummer
  2. 键入 M-x isearch-forward-regexp

    M-x 是表示 Meta-x 的 Emacs 符号,其输入方法如下:通过按住 Alt 键,并同时按 X,然后同时松开两个键;或者按 Esc 键,然后将其松开,然后按 X 键。

  3. 键入要搜索的 regexp: t[a-z]

    由于搜索是增量式的,因此 Emacs 会在键入一个字符的时候就开始突出显示匹配项——在本例中,当您按下 T 键时,会突出显示缓冲区中的所有 T 字符。请注意,只要开始键入带括号的字符列表,突出显示就会消失,Emacs 会显示一条消息,指示输入不足以显示匹配。

    您的 Emacs 会话应该与图 3 中所示类似。

    图 3. 在上下文中显示 regexp 的 Emacs 缓冲区
    在上下文中显示 regexp 的 Emacs 缓冲区
  4. 键入 C-x C-c,退出 Emacs。

    要键入此组合,可以按住 Ctrl 键并同时按下 X,然后按住 Ctrl 键并同时按下 C

isearch-forward-regexpisearch-backward-regexp 功能通常绑定到 M-S-sM-S-r 键盘输入。(如果要输入,请按住 Alt 键和 Ctrl 键,然后按 SR 键。)

仅显示匹配部分,而不显示行

还可以采用另一种方法处理模式上下文中的这个问题,即仅仅输出匹配项本身,而不输出其对应的整个行。可以通过使用 grepsedperl 完成此工作。

使用 grep 仅显示匹配项

--only-matching 选项(或 -o)可更改 grep 的行为,使其不输出包含 regexp 匹配项的整个行,而仅输出匹配项本身。和上面所述的--color 选项一样,此功能在一些较新的 grep 实现中提供,包括 GNU grep(开源程序,可在很多操作系统上使用)。

此选项用于收集匹配 regexp 的数据——非常适合用于收集 IP 地址、URL、名称、电子邮件地址、单词之类的信息——但对了解 regexp 也很有帮助。例如,清单 4 显示了如何使用其收集清单 1 示例文本中的所有单词。将输出每个单词,一个单词占一行。

清单 4. 收集示例文件中的所有单词
$ egrep -o '[A-Za-z]+' midsummer
I
know
a
bank
where
the
wild
thyme
blows
Where
oxlips
and
the
nodding
violet
grows
Quite
over
canopied
with
luscious
woodbine
With
sweet
musk
roses
and
with
eglantine
$

事实上,构造特别复杂的 regexp 来进行特定任务时,通过使用此选项,可非常方便地确保已进行了正确构建。如果 regexp 需要进行调整,通常就可以立即发现。

假定希望输出测试文件中包含字符串 th 的所有单词,则构建清单 5 中所示的 regexp 进行此工作。

清单 5. 示例 1:输出所有包含“th”的单词
$ egrep -o 'th[a-z]*' midsummer
the
thyme
the
th
th
th
$

这样不行。您可以立刻发现输出中有些匹配项根本就不是单词。最好再试一下:清单 6 会考虑单词中任何可能出现在 th 之前的字母。

清单 6. 示例 2:输出所有包含“th”的单词
$ egrep -o '[a-z]*th[a-z]*' midsummer
the
thyme
the
with
ith
with
$

这样好多了,但仍然有些不足。其中有一个“ith”,这表明使用的 regexp 未匹配大写字母。可以通过使用 -i 选项对此进行纠正,如清单 7 中所示。

清单 7. 示例 3:输出所有包含“th”的单词
$ egrep -o -i '[a-z]*th[a-z]*' midsummer
the
thyme
the
with
With
with

大功告成!

通过使用 -o 和一些测试数据,可以帮助进行 regexp 的构建,因为您可能会假定此 regexp 会像匹配包含“th”的行一样工作。但您并不知道表达式实际上有一点差别。

使用 sed 仅显示匹配项

sed 中,可以使用命令:

s/.*\(regexp\).*/\1/p

完成类似的工作,以匹配 sed regexp。此命令仅仅输出输入中匹配的模式,而不会输出包含匹配的行。不过,它仅仅输出给定行的最后一个 实例,如清单 8 中所示。

清单 8. 使用 sed 仅输出匹配字符串
$ sed -n 's/.*\(th[a-z]\).*/\1/p' midsummer
thy
the
$ grep -o th[a-z] midsummer
the
thy
the
$

使用 Perl 仅显示匹配项

regexp 在 Perl 语言中也很常用,但 Perl regexp 与使用 grep 构建的 regexp 不同。通过 pcretest 工具对 Perl regexp 进行测试。可以使用此工具来熟悉兼容 Perl 的正则表达式(Perl-compatible regular expression,PCRE)库和调试或测试使用其构建的 regexp。

regexp 按照通常的方式使用斜线 (/) 字符括起来,可以后跟更改搜索行为的修饰符。表 1 给出了常用的 regexp 修饰符。

表 1. pcretest 的常用 regexp 修饰符
修饰符描述
8此修饰符支持 Unicode (UTF-8) 字符集。
g这个修饰符将搜索全局 匹配(不只一行上)。
i此修饰符会忽略大小写差异。
m此修饰符对多行进行搜索。
x此修饰符使用扩展 Perl regexp。

请尝试以交互方式运行 pcretest,如清单 9 中所示。

清单 9. 使用 pcretest 测试 regexp
$ pcretest
PCRE version 6.7 04-Jul-2006

  re> /[a-z]*th[a-z]*/ig
data> With sweet musk-roses and with eglantine.
 0: With
 0: with
data> Ctrl-c
$

还可以使用输入文件运行 pcretest。输入文件中包含用于测试单行(其后跟着多行待测试数据)的 regexp。通过使用空行进行分割,可以使用多个 regexp 及其相关数据;pcretest 继续读取 regexp 和搜索后面的数据行,直到到达文件末尾为止 (EOF)。

如果提供了第二个文件名,pcretest 会将输出写入该文件中。否则,它将写入到标准输出,如清单 10 中所示。

清单 10. 从输入文件运行 pcretest
$ cat midsummer.pre
/w[hi]|th/gi
I know a bank where the wild thyme blows,
Where oxlips and the nodding violet grows,
Quite over-canopied with luscious woodbine,
With sweet musk-roses and with eglantine. 
$ pcretest midsummer.pre
PCRE version 6.7 04-Jul-2006

/w[hi]|th/gi
I know a bank where the wild thyme blows,
 0: wh
 0: th
 0: wi
 0: th
Where oxlips and the nodding violet grows,
 0: Wh
 0: th
Quite over-canopied with luscious woodbine,
 0: wi
 0: th
With sweet musk-roses and with eglantine. 
 0: Wi
 0: th
 0: wi
 0: th
$

调用向导

txt2regex 脚本是针对 Bash 外壳构建的跨平台的交互型 regexp“向导”。运行该脚本时,会询问一系列关于希望匹配的模式的问题,然后将构建对二十个左右不同应用程序均有效的 regexp:

  • awk
  • ed
  • egrep
  • emacs
  • expect
  • find
  • gawk
  • grep
  • javascript
  • lex
  • lisp
  • mawk
  • mysql
  • ooo
  • perl
  • php
  • postgres
  • procmail
  • python
  • sed
  • tcl
  • vbscript
  • vi
  • vim

除了以交互方式帮助您构建 regexp 外,txt2regex 还提供了各种语言和应用程序的 regexp 语法的简要摘要,用于匹配常见模式的“现成 regexp”和一些 regexp 元字符表。

构建 regexp

为了构建适用于一个或多个 txt2regex 支持的应用程序的 regexp,可以将这些应用程序的名称以逗号分隔的列表的形式作为 --prog 选项的参数提供。

首先,让我们尝试构建在上下文中突出显示匹配项部分的小型 regexp(匹配后面跟一个小写字母的 T 字符):

  1. 启动 txt2regex 并指定构建用于 grepsed 和 Emacs 的 regexp:
    $ txt2regex --prog grep,sed,emacs
  2. 您希望 T 字符位于行中的任何部分,而不仅仅是行首位置,因此在选择“in any part of the line”时键入 2
  3. 再次键入 2,以选择“a specific character”,然后在询问匹配哪个字符时键入 t

    现在必须回答希望的匹配次数。

  4. 键入 1,以指定仅匹配一次。
  5. 选择“a special combination”时,键入 6,然后键入 b 以匹配小写字母,从而指定匹配任意小写字母。键入 .,以退出 combination 子菜单。
  6. 键入 1,以指定仅匹配小写字母一次。

在进行此工作的过程中,txt2regex 将为所选择的三个应用程序分别构建 regexp,并将其显示在屏幕顶部。现在已经选择了自己所希望的准确内容,就应该可能看到所有三个应用程序所需的 regexp,如图 4 中所示。

图 4. 使用 txt2regex 构建 regexp
使用 txt2regex 构建 regexp

键入 ..,以退出。regexp 列表将继续显示在终端上。

是的,所有三个 regexp 碰巧都是完全相同的 (t[a-z]),但这仅仅是因为这个 regexp 很简单,而且所选择的三个应用程序具有类似的 regexp 语法。为所选择的应用程序构建的 regexp 并非总是完全相同。

例如,假定您希望构造两个在仅显示匹配部分,而不显示行部分中使用的 regexp。第一个是由大写或小写字母组成的单个单词:

  1. 在不使用选项的情况下启动 txt2regex:
    $ txt2regex
  2. 键入 2,以匹配行的任何部分。
  3. 键入 6 选择特殊组合,然后键入 ab,以选择所有大写和小写字母。
  4. 键入 .,以返回主菜单,然后键入 4,以指定应该匹配一次或多次。

在没有选项的情况下,txt2regex 会缺省为 perlphppostgrespythonsedvim 应用程序构建 regexp。执行上面的步骤时,将发现前四个应用程序使用的 regexp 与清单 4 中的 grep 使用的完全相同,而 sedvim 的 regexp 仅有一点差别。这是因为这些应用程序使用的元字符概念稍微有点差异,如下所述

同样,键入 .. 退出程序;适用于各个应用程序的 regexp 将继续显示在终端上。可以按照显示的情况进行使用,也可以对其进行编辑,以进一步优化。例如,对于包含撇号 (') 字符 &#151 的单词(如 don't、who're、e'er、owner's、'cause、Joe's 等等)如何处理呢?刚刚构建的 regexp 将无法对其进行恰当的匹配,如仅显示匹配项中所示(请参见清单 11)。

清单 11. 不能恰当匹配带撇号的单词
$ echo "Don't miss a word, just 'cause it's wrong." | egrep [A-Za-z]+
Don
t
miss
a
word
just
cause
it
s
wrong
$

您将希望在括起来的列表中添加连字符,并再次运行,如清单 12 中所示。请注意,现在必须给 regexp 加上引号。

清单 12. 恰当匹配带撇号的单词
$ echo "Don't miss a word, just 'cause it's wrong." | egrep "[A-Za-z']+"
Don't
miss
a
word
just
'cause
it's
wrong
$

仅显示匹配部分,而不显示行部分中使用的另一个 regexp 用于在单词中任何位置包括“th”的单个单词。前面已经使用了适用于 egrepsedperl 的 regexp,现在我们将尝试构建适用于普通 grep 的 regexp。

  1. 启动 txt2regex:
    $ txt2regex
  2. 键入 /,以选择程序,然后键入 hkopqstx.,从而仅为 grep 构建 regexp。
  3. 键入 26ab.3,以选择行中任何位置的零个或多个大写或小写字母。
  4. 键入 2t12h1,以指定其后跟字符 T 和 H,每个字符仅出现一次。
  5. 键入 6ab.3,以指定可在其后跟零个或多个大写或小写字母。
  6. 键入 ..,以退出程序。

现在可以对刚刚构建的 regexp 进行测试,如清单 13 中所示。

清单 13. 使用 grep 匹配包含“th”的单词
$ grep -o [A-Za-z]*th[A-Za-z]* midsummer
the
thyme
the
with
With
with
$

获得 regexp 选项摘要

--showinfo 选项可直接输出关于构建特定程序或语言的 regexp 的信息的简短摘要。输出中将提供应用程序的名称和版本、regexp 元字符、缺省转义元字符、需要缺省转义的元字符、是否可以在括起来的列表中使用制表符以及是否支持可移植操作系统接口(Portable Operating System Interface,POSIX)括号表达式。

如果您是使用多个应用程序的开发人员,则可以通过此方法快速获得特定应用程序的 regexp 规则摘要,如清单 14 中所示。

清单 14. 使用 txt2regex 获得 regexp 规则摘要
$ txt2regex --showinfo javascript

   program  javascript: netscape-4.77
     metas  . [] [^] * + ? {} | ()
  esc meta  \
  need esc  \.*[{(|+?^$
  \t in []  YES
 [:POSIX:]  NO

$ txt2regex --showinfo php

   program  php: 4.0.6
     metas  . [] [^] * + ? {} | ()
  esc meta  \
  need esc  \.*[{(|+?^$
  \t in []  YES
 [:POSIX:]  YES

$

获取现成 regex

其作者将 --make 选项称为“止痛药”。它可以输出作为参数给出的多个常见模式之一的 regexp,如表 2 中所示。

表 2. txt2regex 中的现成 regexp
参数描述
date此参数匹配格式为 mm/dd/yyyy 的日期,从 00/00/0000 到 99/99/9999。
date2此参数匹配格式为 mm/dd/yyyy 的日期,从 00/00/1000 到 19/39/2999。
date3此参数匹配格式为 mm/dd/yyyy 的日期,从 00/00/1000 到 12/31/2999。
hour此参数匹配格式为 hh:mm 的时间,从 00:00 到 99:99。
hour2此参数匹配格式为 hh:mm 的时间,从 00:00 到 29:59。
hour3此参数匹配格式为 hh:mm 的时间,从 00:00 到 23:59。
number此参数匹配任何正负整数。
number2此参数匹配带有可选浮点值的任意正负整数。
number3此参数匹配带有可选逗号和可选浮点值的正负整数。

例如,你可以使用此工具来获取用于军事时间中任意有效时间的现成 regexp,如清单 15 中所示。

清单 15. 使用 txt2regex 获得日期 regexp
$ txt2regex --make hour3

 RegEx perl    : ([01][0-9]|2[0123]):[012345][0-9]
 RegEx php     : ([01][0-9]|2[0123]):[012345][0-9]
 RegEx postgres: ([01][0-9]|2[0123]):[012345][0-9]
 RegEx python  : ([01][0-9]|2[0123]):[012345][0-9]
 RegEx sed     : \([01][0-9]\|2[0123]\):[012345][0-9]
 RegEx vim     : \([01][0-9]\|2[0123]\):[012345][0-9]

$

了解元字符

另一个有用的 txt2regex 选项是 --showmeta,此选项可输出包含为所支持的应用程序构建 regexp 时使用的所有元字符的表。此选项如清单 16 中所示。

清单 16. 使用 txt2regex 显示所有元字符
$ txt2regex --showmeta

       awk       +       ?               |      ()
        ed      \+      \?    \{\}      \|    \(\)
     egrep       +       ?      {}       |      ()
     emacs       +       ?              \|    \(\)
    expect       +       ?               |      ()
      find       +       ?              \|    \(\)
      gawk       +       ?      {}       |      ()
      grep      \+      \?    \{\}      \|    \(\)
javascript       +       ?      {}       |      ()
       lex       +       ?      {}       |      ()
      lisp       +       ?             \\|  \\(\\)
      mawk       +       ?               |      ()
     mysql       +       ?      {}       |      ()
       ooo       +       ?      {}       |      ()
      perl       +       ?      {}       |      ()
       php       +       ?      {}       |      ()
  postgres       +       ?      {}       |      ()
  procmail       +       ?               |      ()
    python       +       ?      {}       |      ()
       sed      \+      \?    \{\}      \|    \(\)
       tcl       +       ?               |      ()
  vbscript       +       ?      {}       |      ()
        vi   \{1\}  \{01\}    \{\}            \(\)
       vim      \+      \=     \{}      \|    \(\)

NOTE: . [] [^] and * are the same on all programs.

$

研究文档

阅读相关手册将非常有价值。您的系统可能提供了很多有关您可能实现的 regexp 的构建和使用方面的文档(包括手册页)。

例如,grepsed 和其他类似的工具都提供了手册页,描述其 regexp 语法并提供相关示例。如果在系统上安装了 GNU 版本,则可能会提供包含比通常手册页更多的信息的 Info 文档——有时候会在其中安装所有用户手册。例如,如果安装了 GNU sed,并有 info 二进制文件,则可以运行以下命令阅读手册:

$ info sed

Perl 文档(通常与主 Perl 源代码或二进制包分开打包和分发)包含有关 Perl regexp 的完整手册页面:

$ man perlre

还有关于此主题的更多信息。pcrepattern 手册页(随 pcretest 应用程序分发,如上面所述)也对 Perl regexp 进行了描述。

最后,很多 UNIX 系统上提供的 regex 手册页提供了有关构建 POSIX regexp 的信息。此手册页中的信息摘自 Henry Spencer 的 regex 库(请参见参考资料)。

总结

UNIX 系统上提供了大量的工具和方法来构建 regexp。本文仅介绍了其中几个最好的工具。

这些工具提供了强大的功能,用于编写、测试和优化 regexp。在 UNIX 系统上使用这些工具和技术可能是了解如何构建复杂 regexp 的最佳方法。而且这个工作也非常有趣。

参考资料

学习

获得产品和技术

  • IBM 试用软件:从 developerWorks 可直接下载这些试用软件,您可以利用它们开发您的下一个项目。
  • GNU 项目网站:下载用于您的操作系统的免费 GNU grep 副本。
  • PCRE:下载 PCRE 的免费副本。
  • txt2regex 脚本:下载 txt2regex 脚本的免费副本。
  • regex:下载 Henry Spencer 的正则表达式库。

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=AIX and UNIX, Open source
ArticleID=252249
ArticleTitle=了解正则表达式
publish-date=08302007