perl语言——正则表达式

一、正则表达式

在perl里面正则表达式(regular expression)又叫模式(pattern),用来匹配或者不匹配某个字符串的特征模板

二、几个正则中常用的符号

在介绍正则表达式的内容之前,先介绍几个符号。

  1. 元字符 . \ ()

perl里面的某些字符具有特殊含义,叫做元字符。比如:

  1. 点号. : 表示任意一个字符,不包括换行符

  2. 反斜杠 \ : 表示字符本身,忽略它的特殊含义。比如.就表示点号本身,而不是任意一个字符。

  3. 圆括号 () : 将模式分成一个个捕获组。

  4. 量词 + * ?

  5. “+” :表示匹配前面的条目一次或一次以上。

    1
    2
    3
    abc+  : 匹配字符c一次或者一次以上,可以匹配abc,abcc,abccc
    ab[0-9]+ :匹配任意09的数字一次或一次以上,如 abc0 ,abc11,,
    ab(cd)+ : 匹配组(cd)一次或一次以上,如abcd,abcdcd,abcdcdcd,,
  6. “*” : 表示匹配前面条目0次或任意次

  7. “?” : 表示匹配前面条目0次或1次

  8. 模式分组 ()

将模式分成多个捕获组,比如 “ ([a-z]+)([0-9]+.abc) “,将模式单独分出两个组。

在《perl语言入门》书中还讲到,可以利用刚刚从圆括号中匹配到的字符再次匹配。比如

1
2
3
4
5
6
7
8
9
10
11
$_ = "abba"  ## 正则表达式默认对变量 $_ 进行匹配
if( m/(.)\1/ ) { # m// 是模式的界定符,下面讲
# 这里会匹配 bb,\1 的意思是再次匹配第一个捕获组的内容,匹配了两次b
print "ok\n";
}

## 如果存在多个圆括号,如何区分哪个括号是第几组?依次计算左括号的序号就可以
$_ = "yabba dabba doo"
if (m/y((.)(.)\3\2) d\1/){ # yabba dabba
print "ok\n";
}

也可以将\1 \2写成 \g{1} \g{2},含义一样,又不容易误解
4. **则一匹配 | **

要么匹配左边要么匹配右边
5. **字符集 [] **
存在[]中的一个字符

1
2
[a-z]  : 任意az的一个字符
[a-z0-9] ,[a-zA-Z],都可以

字符集简写

  1. \d : 表示一个数字
  2. \s : 表示一个空格
  3. 脱字符 ^
    脱字符用在字符集符号[]中,表示不包含这些内容。
    1
    2
    3
    4
    5
    [^a-z] :不包含az
    [^\d] : 不包含数字 , 等价于 [\D]
    [^\s] : 不包含空格 等价于[\S]
    [\d\D] : 匹配任意字符,包括换行符
    [^\d\D] : 不匹配任何字符

三、用正则表达式进行匹配

1. 模式定界符 m//

前面程序中经常出现 m / 模式 /,其中// 就是模式的定界符,斜杠可以用其他符号,比如,#等 m## ,m*

2. 模式匹配修饰符

模式匹配修饰符(modifier)又叫标志(flag),用在模式定界符后面,用来改变默认的匹配行为的。

  1. /i :大小写无关的匹配,这里的/i,/是m//中的/,如果是m##,那么就是#i
    1
    2
    $_ = "ABC";
    m/abc/i ; # 也能匹配成功
  2. /s : 匹配任意字符

原来点号. 不能匹配换行符,现在也可以了
3. /x :可以随便加入空白符

为了便于阅读,可以在模式中随便加空白符。但是这样的原先模式中存在的空白符就没用了,得换成\s形式
4. 还可以选择字符的解释方式 /a /u /l

但我不是很明白什么意思

这几种模式修饰符可以同时使用

3. 锚位
  1. 开头结尾定位^ $ \A \Z

perl5中

\A匹配字符串的绝对开头,这个字符串中间可能有换行符。\z(小写)匹配字符串的绝对末尾,\Z(大写)也是匹配字符串绝对末尾,但是可以允许后面有换行符。

^也是匹配字符串的开头,$匹配字符串的末尾。后面会讲可以用$和/m对多行内容进行匹配,这时^匹配一行开头,$匹配一行结尾。

  1. 单词定位 \b \b
    1
    2
    3
    $_ = "abc";
    m/ \babc\b /x; # 匹配单词abc,
    m/ \bab\b /x; # 匹配失败,没有ab的单词,只有abc
4. 匹配成功会返回1

可以作为if语句的判断条件,比如下一小节中的例子。

5. 绑定操作符 =~

前面的模式匹配都是自动匹配到默认变量$_上,现在可以指定要匹配的变量。

1
2
3
$str = "abc";
if($str =~ m/ \babc\b /x) # 匹配$str变量中的abc
{print "ok\n";}

6. 模式中内插

可以将模式内容先 保存在某个变量里,然后用在模式定界符内

1
2
3
4
5
6
$str = "abc";
$pattern = "\babc\b";
if($str =~ m/$pattern/x)
{
print "ok\n";
}

7. 捕获变量

捕获变量是用来保存捕获组中捕获到的内容。

  1. 默认的捕获变量

默认的捕获变量是$1,$2,这些,用来保存捕获组1,捕获组2,,中的内容,捕获组是第几个组是看左括号是第几个。

1
2
3
4
5
6
$str = "hello world";
if($str =~ m/ (\bhello\b)\s(\bworld\b) /x){
print $1," ",$2;
# 第一个捕获组得到的内容存在$1,第二个存在$2,
# 打印输出 hello world
}

  1. 捕获变量的生存期

这些捕获变量能够保存到下次捕获成功,也就是说如果匹配失败,不会改变上次匹配成功捕获的内容。
3. 不捕获模式

如果不想捕获某个捕获组中的内容,在这个组的左括号后加?:

1
2
3
4
5
6
7
$str = "hello world";
if($str =~ m/ (?:\bhello\b)\s(\bworld\b) /x){
print $1," ",$2;
# 第一个捕获组不捕获,第二个捕获到world存在变量$1,
# 打印输出 “world ”
# 变量$2没有内容
}

4. 自己指定(命名)捕获变量 %+

perl5.10中增加了对捕获内容直接命名的方法,将捕获的内容存在一个哈希表中 %+,表中的键是认为设定的捕获变量,键对应的值是变量内容

比如这样一个捕获组——(?

1
2
3
4
5
6
$str = "hello world";
if($str =~ m/ (?<var1>\bhello\b)\s(?<var2>\bworld\b) /x){
print $+{var1}," ",$+{var2};
# 第一个捕获组捕获hello保存在$var1,第二个捕获到world存在变量$var2,
# 打印输出 “hello world”
}

8. 通用量词
1
模式 m/a{5,10}/ 匹配510次字符a。
9. 保存匹配的结果

​ 在对文件中的行循环匹配的时候,有的时候能匹配到,有的时候匹配不到,如果匹配到就会更新捕获变量,$1,$2或者自己定义的哈希对(这些变量都是只读的)。但如果没匹配到,那么这些变量不会改变,但我们不知道他到底有没有匹配到,可能匹配到了,但他的值跟上一次匹配的一样,那么这些变量还是没变,那怎么办呢?

​ 所以在每次匹配的时候将匹配到的值保存出来,下一次匹配之前再清空他。

1
2
3
4
5
6
7
8
9
$str = "hello world"
push @res,($str =~ m/(hello)/);
print $res[0],"\n";
pop @res;
###也可以
$str =~ m/(hello)/;
$res[0] = $1;
print $res[0],"\n";
pop @res;

四、正则表达式处理文本

1. 用 s/// 进行替换

查找并替换

1
2
3
$str = "bob is a boy\n";
$str =~ s/bob/john/; # 匹配到bob,并用john替换
print $str; # john is a boy

/g进行全局替换,全部都替换

1
2
3
$str = "bob is a boy,bob is a student\n";
$str =~ s/bob/john/;
print $str; # john is a boy,bob is a student 只替换了一个john

1
2
3
$str = "bob is a boy,bob is a student\n";
$str =~ s/bob/john/g;
print $str; # john is a boy,john is a student 两个都替换

** 也可以 s###,等其他的定界符**

大小写转换
\U全部大写 \L全部小写

1
2
3
$str = "bob is a boy,bob is a student\n";
$str =~ s/(bob)/\U$1/g; # 原子匹配到的结果还是默认存在$1,$2中
print $str; # BOB is a boy,BOB is a student 大写
2. split操作符

split操作符,根据字符将字符串分成多组,返回一个数组保存结果

1
2
3
$str = "hello world";
@res = split " ",$str; # 以空格为界切分$str,数组@res保存结果
print $res[0],"\n"; # hello

split默认以空白符切分变量$_

1
2
3
$_str_ = "hello world";
@res = split; # 以空格为界切分$_,数组@res保存结果
print $res[0],"\n"; # hello

3. join函数

与split相反的作用,将多个字符串连接起来

1
2
3
4
5
 $str = "hello world";
@res = split " ",$str;
print $res[0],"\n";
$str = join "+",@res;
print "$str\n"; # hello+world

** join函数连接的列表至少要有两个元素**

4. 列表上下文中的m//

如果m//使用的上下文是列表环境,那么m//将匹配到的所有内容返回成列表

1
2
3
4
5
 $str = "A good day";
my ($first,$second,$third) = $str =~ m/ (\S+)\s(\S+)\s(\S+) /x;
print "$first+$second+$third\n";
## 输出 A+good+day
## 将各个捕获组的内容保存到列表中的变量里

**5. 跨行的模式匹配 /m **

假如有一个文件program.log内容如下.

1
2
3
first line
second line
third line

读取文件内容并修改:

1
2
3
4
5
6
7
8
9
10
open CMD,"< command.log";
$line = join "",<CMD>; # 将文件的所有内容读取到一个变量里
#$line = <CMD>; # 这样写只能读取第一行内容
$line =~ s/^/command.log: /gm; #每一行加上文件名
print $line; # 输出修改后的内容
close CMD;
open CMD,"> com.log";
print CMD $line; # 将修改后的内容保存到另一个文件中
## print后的文件标识符和$line之间没有逗号
close $CMD;

输出:

1
2
3
command.log: first line
command.log: second line
command.log: third line

com.log文件的内容:

1
2
3
command.log: first line
command.log: second line
command.log: third line

如果不加\m,那么com.log中的内容为:

1
2
3
command.log: first line 
second line
third line
6. 一次更新多个文件 $^I变量**

对于一个文件command.log内容如下:

1
2
3
author: li
date: 123
phone: 123

我们想将author改成dong yk,日期设为当前日期,去掉phone那行。

脚本如下:

1
2
3
4
5
6
7
8
9
10
$time = localtime; #perl自己的函数,返回当前时间
print "$time\n";
$^I = ".dat";
while(<>) # 主程序一次读取、更行及输出一行
{
s/^author:.*/author: dong yk/; # 替换
s/^date:.*/date: $time/;
s/^phone:.*\n//; # 删除,包括换行符
print; # 钻石操作符把默认输出设定为打开的文件,print将输出内容写进文件
}

钻石操作符<>会读取命令行参数指定的文件的内容。**$^I变量和<>同时使用,**在<>打开文件的时候会将原来文件复制一份,这个文件以$^I变量内容为后缀。

必须要设置$^I变量,要不然print输出到控制台,因为<>读取的内容默认存在$_中,print默认输出$_的值

7. 从命令行直接编辑

假如有一个文件program.log内容如下.

1
2
3
first line
second line
third line
1
perl -p -i.bak -w -e 's/line/LINE/' command.log

program.log文件内容变成:

1
2
3
first LINE
second LINE
third LINE

-p : 让perl生成一段小程序

-i :功能类似$^I变量,以自定义的后缀保存源文件,这里的是.bak后缀

-w:开启警告

-e :告诉perl后面跟着可执行程序


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!