精通正则表达式:第二章(1)

精通正则表达式:第二章


本文关注的是正则表达式,只是由于Perl对正则表达式的支持优于其余语言,因此选用Perl,请不要过多的关心Perl是怎么回事,必要的前置知识会在这里说起。下面开始咱们的正则之旅。在本文,会使用 · 来代替正则表达式中出现的空格git


1、简单易懂的Perl魔法

下面是一段简单的Perl示例程序,功能是将华氏温度转换为摄氏温度。正则表达式

$celsius = 30;
    $fahrenheit = ($celsius * 9 / 5) + 32;    #计算华氏温度
    print "$celsius C is $fahrenheit F.\n"    #输出两种温度

其结果为:shell

30 C is 86 F.

从这段程序中,咱们会发现Perl的几个特色:oop

  • 普通的变量(如$celsius)以$开头,能够保存数值或者字符串。测试

  • \#表明着注释的开始code

  • 变量能够出如今引号包围的字符串中,最后会被其实际值替代。口怕!对象

同时,Perl也提供了流程控制语句,如while:字符串

$celsius = 20;
    while ($celsius <= 45)
    {
        $fahrenheit = ($celsius * 9 / 5) + 32;
        print "$celsius C is $ fahrenheit F.\n";
        $celsius = $celsius + 5;
    }

运行结果以下:input

20 C is 68 F.
25 C is 77 F.
30 C is 86 F.
35 C is 95 F.
40 C is 104 F.
45 C is 113 F.

当条件为真的时候,while循环控制的部分就会重复执行,直到条件为假。若是在终端中运行,则就像下面这样:it

/~> perl -w CelToFah.pl

这里的-w参数不是必须的,可是加上参数之后,Perl会在可疑的地方报错。这算是一种良好的习惯罢了。因为Perl不是本文的重点,因此介绍就到这里为止,下面是Perl中正则表达式的使用。


2、匹配文本

在Perl中,最简单的正则表达式使用方法就是:检查变量中的文本是否能匹配指定正则表达式。实例片断以下:

if ($reply =~ m/^[0-9]+$/){
            print "only digits\n";
        } else {
            print "not only digits\n";
    }

如你所见,第一行的表达式很有魔法风范:正则表达式是^[0-9]+$m/.../则通知Perl要对正则表达式进行什么操做,m意味着尝试进行正则表达式匹配,而斜杠则用来标记界限;=~则用来链接对象字符串和正则表达式。

须要注意的是,=~===三者请勿混淆。=~用于正则表达式,=用于变量赋值, 而==则用于测试数值是否相等。字符串是否相等,使用的是eq。在这里,表达式

$reply =~ m/^[0-9]+$/

的返回值取决于变量reply。若是其内容能匹配正则表达式m/^[0-9]+$/,则会返回真。而两端的^$则保证其只包含数字。接下来则是两个例子的结合。

首先,会提示用户输入一个值,接受这个输入并用正则表达式去验证:若是输入的是数值,则计算相应的华氏温度;不然报错。实例以下:

print "Enter a temperature in Celsius:\n";
    $celsius = <STDIN>;     #从用户处接受一个输入
    chomp($celsius);        #去掉换行符
    
    if ( $celsius =~ m/^[0-9]+$/) {
        $fahrenheit = ($celsius * 9 / 5) + 32;      #计算华氏温度
        print "$celsius C is $fahrenheit F\n";
    } else {
        print "Expecting a number, so I don't understand \"$celsius\".\n";

}

字符里面的转义就再也不赘述了。要注意的是,Perl中,字符串和正则表达式的区别既不明显,也不重要,这是它和其余语言的一大区别。运行结果以下:

Enter a temperature in Celsius:
123
123 C is 253.4 F

该版本的Perl浮点数处理的很好……那我就不黑了。

更进一步

咱们能够拓展这个例子,使它支持小数和负数。计算部分就交给Perl吧。负数就是一个可选的负号,而小数则是可选的小数点和任意数字。因此拓展后的正则表达式是这样的:

m/^-?[0-9]+(\.[0-9]*)?$/

如今,他就能够匹配-1九、0.343这类的数字了,可是.9834这种数字仍是没法匹配,因为不是什么大问题,咱们会留到很后面再来处理。

成功匹配的反作用

如今,咱们除了要匹配数字,还要用户能够输入C和F来标识输入的温度类型,并进行转换。
咱们知道,正则表达式能够捕获匹配文本,并在可以在正则表达式以外进行引用。而Perl则经过临时变量$1/$2/$3指向分组内的子表达式匹配的文本。
总之,匹配过程当中,使用/1来匹配的文本;而在匹配事后,用$1指向匹配的文本。为此,咱们须要修改表达式。首先,忽略并去掉小数部分的匹配,以突出新特色。

m/^([-+]?[0-9]+)([CF])$/

在这个表达式中,使用括号围住了“有价值”的部分,捕捉事后,咱们能够决定要使用它们来作什么。如今,咱们打算实现以前提到的事情:匹配数字,还要用户能够输入C和F来标识输入的温度类型,并进行转换。

print "Enter a temperature in Celsius:\n";
    $input = <STDIN>;     #从用户处接受一个输入
    chomp($input);        #去掉换行符
    
    if ( $input =~ m/^([-+]?[0-9]+)([CF])$/) {
        #程序运行到这里就已经匹配好了。$1保存数字,$2保存符号。
        $InputNum = $1;
        $type = $2;
        
        if ($type eq "C") {
            #输入为摄氏温度,计算华氏温度
            $celsius = $InputNum;
            $fahrenheit = ($celsius * 9 / 5) + 32;
        } else {
            #不然,应该是"F",那就计算摄氏温度。
            $fahrenheit = $InputNum;
            $celsius = ($fahrenheit - 32) *5 /9;
        }
        #如今获得两个温度值,显示结果,并使用格式化字符串。
        printf "%.2f C is %.2f F.\n", $celsius, $fahrenheit ;
    } else{
        #若是一开始没有匹配,则报错。
        print "Expecting a number followed by \"C\" or \"F\",\n";
        print "So I don't understand \"$input\".\n";
    }

结果以下:

PS E:\LearnPerl> perl -w .\REdigits1.pl
Enter a temperature in Celsius:
22F
-5.56 C is 22.00 F.
PS E:\LearnPerl> perl -w .\REdigits1.pl
Enter a temperature in Celsius:
39C
39.00 C is 102.20 F.
PS E:\LearnPerl> perl -w .\REdigits1.pl
Enter a temperature in Celsius:
oops
Expecting a number followed by "C" or "F",
So I don't understand "oops".

但这里离成功还有必定距离,好比:

  • 没法接受浮点数

  • 不能允许小写的c和f

  • 不能接受数字和字母之间的空格

为了遇上这些距离,咱们还有几件事情要作。首先,咱们向正则表达式添加小数部分的匹配。修改以下:
m/^([-+]?[0-9]+(\.[0-9]*)?)([CF])$/
这里,我在小数部分添加了一个括号。括号自己虽然没有被咱们使用,可是确实影响了引用捕获文本的变量。如今,结果变成了下面这样:

Enter a temperature in Celsius:
11.2F
type is .2
InputNum is 11.2

能够明显的看到,$1匹配的整个数字,也就是外围的第一个括号分组([-+]?[0-9]+(\.[0-9]*)?);而$2则匹配第一个括号分组嵌套(\.[0-9]*);$3则是原来的变量$2。这样就能够明白,分组的序号由分组的开括号(在 表达式中的顺序有关(从左到右)。
咱们如今能够将$type变量的赋值改成$3,或者,使用非捕获型括号

可使用(?:...)来表示只分组,不捕获。这样,一是不会影响捕获计数,二是能够提升匹配效率,三是让代码更加清晰。可是,若是是只使用一次的正则,能够考虑弃之不用。

如今,咱们能够来处理空格了。咱们可使用·*来表示。再度修改以下:
m/^([-+]?[0-9]+(?:\.[0-9]*)?) *([CF])$/
有人注意到哪里修改了吗?嗯……这样确实很难注意到这边有一个空格。与此同时,若是输入的是制表符(天知道为何会输入进来),那就匹配不到了。因此,咱们可使用元字符\s来匹配空白字符,三度修改以下:
m/^([-+]?[0-9]+(?:\.[0-9]*)?)\s*([CF])$/
好了,如今只剩下小写字母的问题了。咱们可使用一个修饰符(modifier)。
结果变成这样:m/^([-+]?[0-9]+(?:\.[0-9]*)?)\s*([CF])$/i`这个修饰符只是Perl中的用法,其余语言有不一样的实现方式,如Python使用的在编译的时候指定。
如今,大功告成,来测试一下:

Enter a temperature in Celsius:
33.98 c
1.10 C is 33.98 F.

结果不尽如人意……嗯,再修改一下便可。
最终版本就是这样子的:

print "Enter a temperature in Celsius:\n";
    $input = <STDIN>;     #从用户处接受一个输入
    chomp($input);        #去掉换行符
    
    if ( $input =~ m/^([-+]?[0-9]+(?:\.[0-9]*)?)\s*([CF])$/i) {
        #程序运行到这里就已经匹配好了。$1保存数字,$2保存符号。
        $InputNum = $1;
        $type = $2;
        
        if ($type =~ m/c/i) {
            #输入为摄氏温度,计算华氏温度
            $celsius = $InputNum;
            $fahrenheit = ($celsius * 9 / 5) + 32;
        } else {
            #不然,应该是"F",那就计算摄氏温度。
            $fahrenheit = $InputNum;
            $celsius = ($fahrenheit - 32) *5 /9;
        }
        #如今获得两个温度值,显式结果。
        printf "%.2f C is %.2f F.\n", $celsius, $fahrenheit ;
    } else{
        #若是一开始没有匹配,则报错。
        print "Expecting a number followed by \"C\" or \"F\",\n";
        print "So I don't understand \"$input\".\n";
    }

到这里就先休息下吧。顺便来道题目思考思考:

(·*|\t*)[·\t]*之间在匹配的结果有什么差异?

相关文章
相关标签/搜索