内容

名称

perlrequick - Perl 正则表达式快速入门

描述

本页介绍了在 Perl 中理解、创建和使用正则表达式(“regexes”)的入门知识。

指南

本页假设您已经了解一些知识,例如什么是“模式”以及使用它们的语法。如果您不了解,请参阅 perlretut

简单词匹配

最简单的正则表达式只是一个词,或者更一般地说,是一串字符。由一个词组成的正则表达式匹配包含该词的任何字符串

"Hello World" =~ /World/;  # matches

在这个语句中,World 是一个正则表达式,而 // 包含 /World/ 告诉 Perl 在字符串中搜索匹配项。运算符 =~ 将字符串与正则表达式匹配关联起来,如果正则表达式匹配则产生真值,如果正则表达式不匹配则产生假值。在我们的例子中,World"Hello World" 中的第二个词匹配,所以表达式为真。这个想法有几个变体。

像这样的表达式在条件语句中很有用

print "It matches\n" if "Hello World" =~ /World/;

可以使用 !~ 运算符反转匹配的意义

print "It doesn't match\n" if "Hello World" !~ /World/;

正则表达式中的字面字符串可以用变量替换

$greeting = "World";
print "It matches\n" if "Hello World" =~ /$greeting/;

如果你要匹配 $_,可以省略 $_ =~ 部分

$_ = "Hello World";
print "It matches\n" if /World/;

最后,匹配的默认分隔符 // 可以通过在前面添加一个 'm' 来更改为任意分隔符

"Hello World" =~ m!World!;   # matches, delimited by '!'
"Hello World" =~ m{World};   # matches, note the matching '{}'
"/usr/bin/perl" =~ m"/perl"; # matches after '/usr/bin',
                             # '/' becomes an ordinary char

正则表达式必须完全匹配字符串的一部分,才能使语句为真

"Hello World" =~ /world/;  # doesn't match, case sensitive
"Hello World" =~ /o W/;    # matches, ' ' is an ordinary char
"Hello World" =~ /World /; # doesn't match, no ' ' at end

Perl 将始终在字符串中最早可能的位置进行匹配

"Hello World" =~ /o/;       # matches 'o' in 'Hello'
"That hat is red" =~ /hat/; # matches 'hat' in 'That'

并非所有字符都可以在匹配中“按原样”使用。一些字符,称为 **元字符**,被认为是特殊的,并保留用于正则表达式表示法。元字符是

{}[]()^$.|*+?\

可以通过在元字符前面加上反斜杠来匹配元字符的字面意义

"2+2=4" =~ /2+2/;    # doesn't match, + is a metacharacter
"2+2=4" =~ /2\+2/;   # matches, \+ is treated like an ordinary +
'C:\WIN32' =~ /C:\\WIN/;                       # matches
"/usr/bin/perl" =~ /\/usr\/bin\/perl/;  # matches

在最后一个正则表达式中,正斜杠 '/' 也被反斜杠转义,因为它用于分隔正则表达式。

大多数元字符并不总是特殊的,而其他字符(例如分隔模式的字符)在各种情况下会变得特殊。这可能会令人困惑,并导致意外结果。 use re 'strict' 可以通知您潜在的陷阱。

不可打印的 ASCII 字符由 **转义序列** 表示。常见的例子有 \t 表示制表符,\n 表示换行符,\r 表示回车符。任意字节由八进制转义序列表示,例如 \033,或十六进制转义序列表示,例如 \x1B

"1000\t2000" =~ m(0\t2)  # matches
"cat" =~ /\143\x61\x74/  # matches in ASCII, but
                         # a weird way to spell cat

正则表达式主要被视为双引号字符串,因此变量替换有效

$foo = 'house';
'cathouse' =~ /cat$foo/;   # matches
'housecat' =~ /${foo}cat/; # matches

对于上面所有的正则表达式,如果正则表达式在字符串中的任何位置匹配,则被认为是匹配。为了指定它应该匹配的位置,我们将使用 **锚点** 元字符 ^$。锚点 ^ 表示在字符串的开头匹配,锚点 $ 表示在字符串的结尾匹配,或者在字符串结尾的换行符之前匹配。一些例子

"housekeeper" =~ /keeper/;         # matches
"housekeeper" =~ /^keeper/;        # doesn't match
"housekeeper" =~ /keeper$/;        # matches
"housekeeper\n" =~ /keeper$/;      # matches
"housekeeper" =~ /^housekeeper$/;  # matches

使用字符类

字符类允许在正则表达式中的特定位置匹配一组可能的字符,而不是单个字符。字符类有多种类型,但通常人们使用这个术语时,指的是本节中描述的类型,它们在技术上被称为“方括号字符类”,因为它们用方括号[...]表示,其中包含要匹配的字符集。但为了与常用用法一致,我们将在下面省略“方括号”。以下是一些(方括号)字符类的示例

/cat/;            # matches 'cat'
/[bcr]at/;        # matches 'bat', 'cat', or 'rat'
"abc" =~ /[cab]/; # matches 'a'

在最后一个语句中,即使'c'是类中的第一个字符,正则表达式可以匹配的最早位置是'a'

/[yY][eE][sS]/; # match 'yes' in a case-insensitive way
                # 'yes', 'Yes', 'YES', etc.
/yes/i;         # also match 'yes' in a case-insensitive way

最后一个示例显示了与'i'修饰符的匹配,它使匹配不区分大小写。

字符类也有普通字符和特殊字符,但字符类内部的普通字符和特殊字符集与字符类外部的字符集不同。字符类的特殊字符是-]\^$,并且使用转义匹配

/[\]c]def/; # matches ']def' or 'cdef'
$x = 'bcr';
/[$x]at/;   # matches 'bat, 'cat', or 'rat'
/[\$x]at/;  # matches '$at' or 'xat'
/[\\$x]at/; # matches '\at', 'bat, 'cat', or 'rat'

特殊字符'-'在字符类中充当范围运算符,因此笨拙的[0123456789][abc...xyz]变为简洁的[0-9][a-z]

/item[0-9]/;  # matches 'item0' or ... or 'item9'
/[0-9a-fA-F]/;  # matches a hexadecimal digit

如果'-'是字符类中的第一个或最后一个字符,则它被视为普通字符。

特殊字符^在字符类的第一个位置表示否定字符类,它匹配除方括号中的字符之外的任何字符。[...][^...]都必须匹配一个字符,否则匹配失败。然后

/[^a]at/;  # doesn't match 'aat' or 'at', but matches
           # all other 'bat', 'cat, '0at', '%at', etc.
/[^0-9]/;  # matches a non-numeric character
/[a^]at/;  # matches 'aat' or '^at'; here '^' is ordinary

Perl 有几个用于常见字符类的缩写。(这些定义是 Perl 在使用/a修饰符的 ASCII 安全模式下使用的。否则,它们也可能匹配更多非 ASCII Unicode 字符。有关详细信息,请参阅"perlrecharclass 中的转义序列"。)

\d\s\w\D\S\W 缩写可以在字符类内部和外部使用。以下是一些正在使用的示例

/\d\d:\d\d:\d\d/; # matches a hh:mm:ss time format
/[\d\s]/;         # matches any digit or whitespace character
/\w\W\w/;         # matches a word char, followed by a
                  # non-word char, followed by a word char
/..rt/;           # matches any two chars, followed by 'rt'
/end\./;          # matches 'end.'
/end[.]/;         # same thing, matches 'end.'

单词锚 \b 匹配单词字符和非单词字符\w\W\W\w之间的边界

$x = "Housecat catenates house and cat";
$x =~ /\bcat/;  # matches cat in 'catenates'
$x =~ /cat\b/;  # matches cat in 'housecat'
$x =~ /\bcat\b/;  # matches 'cat' at end of string

在最后一个示例中,字符串的结尾被视为单词边界。

对于自然语言处理(例如,将撇号包含在单词中),请改用 \b{wb}

"don't" =~ / .+? \b{wb} /x;  # matches the whole string

匹配这个或那个

我们可以使用交替元字符 '|' 匹配不同的字符串。要匹配 dogcat,我们形成正则表达式 dog|cat。与之前一样,Perl 将尝试在字符串中尽早匹配正则表达式。在每个字符位置,Perl 将首先尝试匹配第一个备选方案 dog。如果 dog 不匹配,Perl 将尝试下一个备选方案 cat。如果 cat 也不匹配,则匹配失败,Perl 将移动到字符串中的下一个位置。一些例子

"cats and dogs" =~ /cat|dog|bird/;  # matches "cat"
"cats and dogs" =~ /dog|cat|bird/;  # matches "cat"

即使 dog 是第二个正则表达式中的第一个备选方案,cat 也能够在字符串中更早地匹配。

"cats"          =~ /c|ca|cat|cats/; # matches "c"
"cats"          =~ /cats|cat|ca|c/; # matches "cats"

在给定的字符位置,允许正则表达式匹配成功的第一个备选方案将是匹配的方案。这里,所有备选方案都在第一个字符串位置匹配,所以第一个匹配。

分组和层次匹配

分组元字符 () 允许将正则表达式的部分视为一个单元。正则表达式的部分通过将它们括在括号中来分组。正则表达式 house(cat|keeper) 表示匹配 house 后跟 catkeeper。以下是一些其他示例

/(a|b)b/;    # matches 'ab' or 'bb'
/(^a|b)c/;   # matches 'ac' at start of string or 'bc' anywhere

/house(cat|)/;  # matches either 'housecat' or 'house'
/house(cat(s|)|)/;  # matches either 'housecats' or 'housecat' or
                    # 'house'.  Note groups can be nested.

"20" =~ /(19|20|)\d\d/;  # matches the null alternative '()\d\d',
                         # because '20\d\d' can't match

提取匹配项

分组元字符 () 还允许提取匹配字符串的部分。对于每个分组,匹配的部分将进入特殊变量 $1$2 等。它们可以像普通变量一样使用

# extract hours, minutes, seconds
$time =~ /(\d\d):(\d\d):(\d\d)/;  # match hh:mm:ss format
$hours = $1;
$minutes = $2;
$seconds = $3;

在列表上下文中,带有分组的匹配 /regex/ 将返回匹配值的列表 ($1,$2,...)。因此,我们可以将其改写为

($hours, $minutes, $second) = ($time =~ /(\d\d):(\d\d):(\d\d)/);

如果正则表达式中的分组是嵌套的,$1 获取具有最左侧开括号的分组,$2 获取下一个开括号,等等。例如,以下是一个复杂的正则表达式,以及它下面的匹配变量

/(ab(cd|ef)((gi)|j))/;
 1  2      34

与匹配变量 $1$2 等相关联的是反向引用 \g1\g2 等。反向引用是匹配变量,可以在正则表达式内部使用

/(\w\w\w)\s\g1/; # find sequences like 'the the' in string

$1$2 等仅应在正则表达式之外使用,而 \g1\g2 等仅应在正则表达式内部使用。

匹配重复

量词 元字符 ?*+{} 允许我们确定正则表达式中我们认为匹配的部分的重复次数。量词紧跟在我们要指定的字符、字符类或分组之后。它们具有以下含义

以下是一些示例

/[a-z]+\s+\d*/;  # match a lowercase word, at least some space, and
                 # any number of digits
/(\w+)\s+\g1/;    # match doubled words of arbitrary length
$year =~ /^\d{2,4}$/;  # make sure year is at least 2 but not more
                       # than 4 digits
$year =~ /^\d{ 4 }$|^\d{2}$/; # better match; throw out 3 digit dates

这些量词将尝试匹配尽可能多的字符串,同时仍然允许正则表达式匹配。因此我们有

$x = 'the cat in the hat';
$x =~ /^(.*)(at)(.*)$/; # matches,
                        # $1 = 'the cat in the h'
                        # $2 = 'at'
                        # $3 = ''   (0 matches)

第一个量词 .* 尽可能多地获取字符串,同时仍然使正则表达式匹配。第二个量词 .* 没有剩余的字符串,因此它匹配 0 次。

更多匹配

关于匹配运算符,您可能还需要了解一些其他内容。全局修饰符 /g 允许匹配运算符在字符串中尽可能多次匹配。在标量上下文中,对字符串的连续匹配将使 /g 在匹配之间跳转,在它进行的过程中跟踪字符串中的位置。您可以使用 pos() 函数获取或设置位置。例如,

$x = "cat dog house"; # 3 words
while ($x =~ /(\w+)/g) {
    print "Word is $1, ends at position ", pos $x, "\n";
}

打印

Word is cat, ends at position 3
Word is dog, ends at position 7
Word is house, ends at position 13

匹配失败或更改目标字符串将重置位置。如果您不希望在匹配失败后重置位置,请添加 /c,如 /regex/gc

在列表上下文中,/g 返回匹配分组的列表,或者如果没有分组,则返回对整个正则表达式的匹配列表。所以

@words = ($x =~ /(\w+)/g);  # matches,
                            # $word[0] = 'cat'
                            # $word[1] = 'dog'
                            # $word[2] = 'house'

搜索和替换

搜索和替换使用 s/regex/replacement/modifiers 执行。replacement 是一个 Perl 双引号字符串,它用与 regex 匹配的内容替换字符串中的内容。运算符 =~ 也用于将字符串与 s/// 关联。如果与 $_ 匹配,则可以删除 $_ =~。如果匹配,s/// 返回执行的替换次数;否则返回 false。以下是一些示例

$x = "Time to feed the cat!";
$x =~ s/cat/hacker/;   # $x contains "Time to feed the hacker!"
$y = "'quoted words'";
$y =~ s/^'(.*)'$/$1/;  # strip single quotes,
                       # $y contains "quoted words"

使用 s/// 运算符,匹配的变量 $1$2 等立即可用于替换表达式。使用全局修饰符,s///g 将搜索并替换字符串中正则表达式的所有出现

$x = "I batted 4 for 4";
$x =~ s/4/four/;   # $x contains "I batted four for 4"
$x = "I batted 4 for 4";
$x =~ s/4/four/g;  # $x contains "I batted four for four"

非破坏性修饰符 s///r 会返回替换的结果,而不是修改 $_(或任何使用 =~ 绑定到替换的变量)。

$x = "I like dogs.";
$y = $x =~ s/dogs/cats/r;
print "$x $y\n"; # prints "I like dogs. I like cats."

$x = "Cats are great.";
print $x =~ s/Cats/Dogs/r =~ s/Dogs/Frogs/r =~
    s/Frogs/Hedgehogs/r, "\n";
# prints "Hedgehogs are great."

@foo = map { s/[a-z]/X/r } qw(a b c 1 2 3);
# @foo is now qw(X X X 1 2 3)

评估修饰符 s///e 会在替换字符串周围包装一个 eval{...},并且评估结果将替换匹配的子字符串。以下是一些示例

# reverse all the words in a string
$x = "the cat in the hat";
$x =~ s/(\w+)/reverse $1/ge;   # $x contains "eht tac ni eht tah"

# convert percentage to decimal
$x = "A 39% hit rate";
$x =~ s!(\d+)%!$1/100!e;       # $x contains "A 0.39 hit rate"

最后一个示例表明 s/// 可以使用其他分隔符,例如 s!!!s{}{},甚至 s{}//。如果使用单引号 s''',则正则表达式和替换将被视为单引号字符串。

分割运算符

split /regex/, stringstring 分割成一个子字符串列表并返回该列表。正则表达式决定 string 相对于其分割的字符序列。例如,要将字符串分割成单词,请使用

$x = "Calvin and Hobbes";
@word = split /\s+/, $x;  # $word[0] = 'Calvin'
                          # $word[1] = 'and'
                          # $word[2] = 'Hobbes'

要提取逗号分隔的数字列表,请使用

$x = "1.618,2.718,   3.142";
@const = split /,\s*/, $x;  # $const[0] = '1.618'
                            # $const[1] = '2.718'
                            # $const[2] = '3.142'

如果使用空正则表达式 //,则字符串将被分割成单个字符。如果正则表达式有分组,则生成的列表还包含来自分组的匹配子字符串

$x = "/usr/bin";
@parts = split m!(/)!, $x;  # $parts[0] = ''
                            # $parts[1] = '/'
                            # $parts[2] = 'usr'
                            # $parts[3] = '/'
                            # $parts[4] = 'bin'

由于 $x 的第一个字符与正则表达式匹配,split 在列表之前添加了一个空的初始元素。

use re 'strict'

在 v5.22 中新增,这在编译正则表达式模式时应用比其他情况更严格的规则。它可以找到一些合法但可能不是你想要的东西。

参见 'strict' in re.

BUGS

无。

参见

这只是一个快速入门指南。有关正则表达式的更深入教程,请参见 perlretut,有关参考页,请参见 perlre.

作者和版权

版权所有 (c) 2000 Mark Kvale 保留所有权利。

本文件可以在与 Perl 本身相同的条款下分发。

致谢

作者要感谢 Mark-Jason Dominus、Tom Christiansen、Ilya Zakharevich、Brad Hughes 和 Mike Giroux 对他们所有有益的评论。