解决方法Perl regexp限制?

时间:2013-01-25 02:01:28

标签: regex perl size

我编写了一个从邮件文件夹中提取附件的程序( GITHUB) 但由于Perl对正则表达式匹配的32767行限制,它失败了。我的 程序将每个邮件消息作为单个字符串加载,然后尝试 将每个base64编码的文件作为单个字符串进行匹配。

要复制问题,请先执行此操作:

(dd if=/dev/urandom bs=2000 count=1000 | base64 ; echo "\n\n\n" ; dd if=/dev/urandom bs=2000 count=1000 | base64 ) >! /tmp/testfile.txt 

这将创建一个包含的单个5403516字节文件 base64编码的两个文件之间有三个换行缓冲区 他们。生产情况稍微复杂一点,但这一点 更简单的案例证明了这个问题。

我们的目标是提取第一个文件的base64编码。其他 单词,所有50个字符或更长的连续行和 仅包含base64个字符,但在看到第一个“=”时停止 sign(表示base64中的文件结尾)。

/tmp/testfile.txt有70180行,第一行有35088行 表示我们想要捕获的字符串(base64-encode) 第一个文件)。

我们现在在Perl中执行以下操作:

# next 4 lines: read the entire file into a single variable 
undef $/; 
open(A,"/tmp/testfile.txt"); 
$all = <A>; 
close(A); 

# the output of base64 consists of these characters (plus "=" and 
# "\n", but those two are special cases) 
my($chars) = "[a-zA-Z0-9\+\/]"; 

# we declare a subroutine for testing 
sub foo {print STDERR length($_[0]),"\n";} 

# this is what I tried to do originally 
$all=~s/(\n($chars{50,}\=*\n)+)($chars+\=*\n)/foo("$1$3")/seg; 

上述产量为“2523137”,然后是“178467”,然后是“2523137”,然后是“178544” 到STDERR。

换句话说,它捕获第一个2523137个字符 文件,然后是第一个文件的下一个178467个字符,而不是 像我想要的那样捕获第一个文件的所有2701604个字符。注意 2523137约为77 * 32767(并且每行为 /tmp/testfile.txt长度为77个字符)。

@ikegami,如果我理解正确,你的方法是:

$all=~s/((\n($chars{50,}\=*\n){0,20000})+)($chars+\=*\n)//seg; 

换句话说,一次捕获20000行(避免32767行 限制),但捕获多个20000行的串。它是否正确?

由于结果将出现在多个变量中,我没有通过 结果为foo(),而是将结果打印到STDERR之类 这个:

print STDERR "1 is $1\n"; 
print STDERR "2 is $2\n"; 
print STDERR "3 is $3\n"; 
print STDERR "4 is $4\n"; 
print STDERR "5 is $5\n"; 
print STDERR "6 is $6\n"; 

这产生1美元和2美元作为相同的15085线变量,3美元和4美元 不相同的一行变量,$ 5和$ 6为空。

因此,我认为我误解了你的做法。救命?

1 个答案:

答案 0 :(得分:2)

由于您可以通过静态字符串拆分base64部分,因此可以使用$/更有效地拆分文件,然后选择每个部分是否符合您的标准。

use strict;
use warnings;
use autodie;

my $is_base64 = qr{^[a-zA-Z0-9\+\/]+\n?$}m;

{
    open(my $fh,"/tmp/testfile.txt");
    local $/ = "=\n";

    while(my $base64 = <$fh>) {
        chomp $base64;
        _strip(\$base64);
        next unless $base64 =~ $is_base64;

        print STDERR length $base64, "\n";
    }
}

sub _strip {
    my $ref = shift;
    $$ref =~ s{^\s+}{};
    $$ref =~ s{\s+$}{};

    return;
}

这对于拆分邮箱也很方便,将$/设置为"\n\nFrom "

但是评论表明你应该用模块做这件事是正确的。在CPAN上有很多的邮件模块,所以找到合适的邮件模块有点困难。