Perl正则表达式:文本处理

2022-05-05 13:07:16 浏览数 (1)

在上一小节涉及的只是简单匹配模式或者称为查找模式,仅仅是搜索匹配的字段,而如果想要对文本进行处理,则需要利用特定的模式来修改字符串。

⑴替换模式

在正则表达式中s/正则表达式/替换字符/模式可以对特定字符串进行匹配查找并替换(substitution),如果匹配失败则不替换。其中s///可以理解为正则表达式的模式,也可以理解为操作符,其要修改的数据必须储存在变量,使用绑定操作符指定替换操作(不指定则使用默认变量$_),中如下所示:

代码语言:javascript复制
$_ = "He's out bowling with Barney tonight";
s/Barney/Fred/;    #也即$_ =~ s/Barney/Fred/
print "$_n";
s/with (w )/against $1's team/;
print "$_n";

运行结果如下所示:

当然,替换模式的定界符也可以自定义,例如s###、s{}{}、s{}()、s[]##等。

①替换修饰符

可以看出,替换部分也可以使用捕获变量。s///可以直接对变量数据进行修改,其返回值为布尔值,表示是否成功替换。假如一个数据变量有多个可以匹配的字符串,s///默认只替换最前面的一个,可以添加修饰符//g来进行全局替换,并且,m//模式中的修饰符//i、//x、//s在s///中也可以使用,如下所示:

代码语言:javascript复制
$word = "Home, a sweet home";
$word =~ s/home/cave/ig;
print "$wordn";

运行结果如下所示:

假如不想改变原有数据变量内容,将替换后的内容保存为新变量,可以先对变量进行复制然后替换,然而从Perl 5.14开始增添了一个新的修饰符//r,使得s///不改变原数据变量内容,而将替换后的内容作为返回值,如下所示:

代码语言:javascript复制
use 5.014;
my $var1 = "Home, a sweet home";
my $var2 = $var1 =~ s/home/cave/rig;
print "$var1n$var2n";

运行结果如下所示:

②大小写替换

在替换操作中,常会遇到大小写转换。在替换部分可以使用大小写转换操作符,其中U可以将其后内容全部转换为大写,直到字符串末尾或者E;L可以将其后内容全部转换为小写,直到字符串末尾或者E;u和l则只将其后第一个字符转换为大写、小写,同时使用u和L则只大写首字母。具体如下所示:

代码语言:javascript复制
use 5.014;
my $var1 = "I saw Barney with fred.";
my $var2 = $var1 =~ s/(fred|barney)/U$1/rig;
my $var3 = $var1 =~ s/(fred|barney)/u$1/rig;
my $var4 = $var1 =~ s/(fred|barney)/L$1/rig;
my $var5 = $var1 =~ s/(w ) with/U$1 with/r;
my $var6 = $var1 =~ s/(w ) with/U$1E with/r;
my $var7 = $var1 =~ s/(fred|barney)/uL$1/rig;
print "$var1n$var2n$var3n$var4n$var5n$var6n$var7n";

运行结果如下所示:

可以看出U和L会对其后的整个字符串(不是单词)进行转换,灵活的使用E可以只转换其中一部分。

⑵拆分模式

split是拆分模式的正则表达式,会根据模式匹配结果拆分字符串,其第一个参数为正则表达式,第二个参数为要拆分的字符串,返回值为拆分后的子字符串列表,假如有空子字符串,默认开头和中间的会被保留,末尾的会被舍弃,可以添加第三个参数-1改变默认行为,具体如下所示:

代码语言:javascript复制
my @fields = split /:/, "abc:def:ghi";  #数组@fields为qw/ abc def ghi /
my @fields = split /s /, $strings;    #参数也可以是变量
my @fields = split /:/, "::abc:def::";  #@fields为("", "", "abc", "def")

假如不给定任何参数,split默认以空白符/s /分割变量$_。在分割模式下正则表达式里需要避免使用捕获圆括号(分组可以使用(?:)代替)。

①join函数

join函数为split操作符的逆操作,是指通过特定分隔符将不同子字符串连接起来,因为是指定的确定分隔符,所以不需要使用正则表达式进行模糊匹配,因此不是join函数模式操作符。join函数第一个参数为分隔符,第二个参数为要连接的子字符串列表,其返回值为字符串标量,如下所示:

代码语言:javascript复制
my $strings = join ":", abc, def, ghi;    #得到"abc:def:ghi"
my $strings = join "t", (abc, def, ghi);    #得到"abc  def  ghi"
my $strings = join $sep, @fields;      #参数可以使用变量

②匹配列表

我们知道在匹配模式m//中,如果在标量上下文中绑定操作符返回值为布尔值,在列表上下文中返回值为捕获变量的列表;而在全局匹配模式m//g中,匹配字段可以有多个,这时候绑定操作符会依次返回所有匹配的捕获变量(如果没有捕获括号,则返回模式匹配的字符串),那么在列表上下文中返回的就是匹配字段的列表,如下所示:

代码语言:javascript复制
my $text = "Dino saw Barney with Fred.";
my @friends = $text =~ /(w ) with (w )/;
my @names1 = $text =~ /([A-Z](w ))/g;
my @names2 = $text =~ /[A-Z]w /g;
print "$textn@friendsn@names1n@names2n";

其运行结果如下所示:

可以看到,合理的运用括号,可以起到选取特定模式的字符串效果,从而起到和split互补的作用。

⑶贪婪量词与非贪婪量词

与?和{3}这样的量词不同,*和 是贪婪量词,也即在正则表达式中间遇到这两个量词时会首先匹配尽量多的字符,然后再匹配后面的部分,如果后面的不匹配,正则表达式则会以每次吐出一个字符的方式来进行匹配,直至剩余最少字符数(*零个字符 一个字符),这种回溯运行机制有时会使运行速度变慢。*?和 ?则变为非贪婪量词,也即在正则表达式中间遇到这两个量词时会首先匹配尽量少的字符(*?零个开始, ?一个开始),并匹配后面的部分,后面部分不匹配时,正则表达式则会以每次吞一个字符的方式来进行匹配,直至匹配最多的字符,这两种量词的效率因中间字符串的多少而各有优势。除了效率问题之外,其应用也会有所差别,如下所示:

代码语言:javascript复制
use 5.014;
my $text = "I thought you said Fred and <BOLD>Velma</BOLD>, not <BOLD>Wilma</BOLD>.";
my $text1 = $text =~ s#<BOLD>(.*)</BOLD>#$1#rg;
my $text2 = $text =~ s#<BOLD>(.*?)</BOLD>#$1#rg;
print "$text1n$text2n";

运行结果如下所示:

⑷更新文件

在1.2.2.5中我们认识到可以利用自定义句柄来读取文件内容并处理后输出到新文件,如果只是修改更新原有文件,可以利用特殊变量^I,该变量默认值为undef,当其被赋值为特殊的字符串之后,钻石操作符<>会为输入文件的文件名添加一个后缀(这个后缀也即

假使Barney于今天更新了这个program,我们需要修改日志的姓名、日期并删除私人信息电话:

代码语言:javascript复制
#!/usr/bin/perl -w
use strict;
chomp(my $date = `date`);
$^I = ".bak";
while (<>) {
  s/^Author:.*/Author: Barney/;
  s/^Phone:.*n//;
  s/^Date:.*/Date: $date/;
  print;
}

首先钻石操作符会打开参数指定的文件program01.dat,并将其文件名修改为program01.dat.bak,同时将输出设定为program01.dat,这样输出文件与输入文件并不干扰,而program01.dat.bak其实储存的是原有文件的内容,也即Perl实际做的是将文件内容进行掉包,程序运行结束后program01.dat.bak可以看成旧文件的备份文件,其运行以及修改完之后的日志文件如下所示:

这里date为linux系统的date命令,输出的为当前时间加一个换行符,Bash命令放在反单引号内部可以当成变量引用,此外也可以使用Perl自己的localtime函数。

⑸命令行选项

Perl除了可以使用命令行参数外,也可以使用命令行选项,常见的命令行选项如下所示:

-h:打印Perl的命令选项列表;

-v:打印Perl的版本信息;

-c:只检查Perl脚本语法,而不执行脚本;

-w:打印警告信息,包括错误使用保留字、文件句柄、子程序等情况;

-e:后加Perl命令(放在单引号内),用于在命令行直接执行Perl命令,多个命令之间以分号;隔开;

-n:使Perl隐式地循环遍历指定的文件或输入内容,可自行决定输出内容,自动循环,相当于 while(<>) { 脚本; };

-p:使Perl隐式地循环遍历指定的文件,同时打印所有的行。自动循环 自动输出,相当于 while(<>) { 脚本; print; };

-a:可与-n或者-p一起使用,负责打开自动拆分模式,用于对字符串以指定分隔符进行隐式拆分,默认为以空白符拆分,拆分后的字符串列表保存到@F中,相当于@F=split '字符串',分隔符可以使用-F参数指定;

-F:其后直接加分隔符或者模式,放在-a参数之前,模式是位于斜杠、单引号或双引号之间的正则表达式。例如-F/: /表示以一个或多个冒号拆分输入行;

-i:其后加备份文件扩展名,在使用<>循环遍历文件时启用原位编辑模式,相当于给变量$^I赋值。如果没有规定扩展名的话,则原位修改各行内容,否则使用扩展名来修改输入文件名(以便充当备份文件),并使用原位编辑的原文件名创建输出文件。

Perl的命令行选项可以很便捷的实现某些功能,如下所示:

对于前面更新文件的脚本,可以简单使用下面命令来执行:

代码语言:javascript复制
perl -i.bak -pe 's/^Author:.*/Author: Barney/;s/^Phone:.*n//;s/^Date:.*/Date: Fri Aug 25 11:17:05 CST 2017/' program01.dat

注意,Perl的命令行选项顺序并不是可以随意打乱的,-e一定要紧跟着单引号内的命令。

0 人点赞