2

我编写了一个程序来从邮件文件夹( 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 字符的连续行,但在我们看到第一个“=”符号(表示 base64 中的文件结束)时停止。

/tmp/testfile.txt 有 70180 行,前 35088 行代表我们要捕获的字符串(第一个文件的 base64 编码)。

我们现在在 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 是空的。

因此,我想我误解了你的方法。帮助?

4

1 回答 1

1

由于您可以通过静态字符串拆分 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 上有很多邮件模块,因此很难找到合适的。

于 2013-01-25T05:10:46.393 回答