Perl中如何覆写被其他类调用的类?以MIME::Parser场景为例
问题
使用MIME::Parser解析大量邮件时,希望每封邮件存入独立子目录,且目录内文件命名规则一致,但遇到两个问题:
- 无命名MIME部件的文件名会被强制加入进程ID
- 无命名部件的编号全局递增,新邮件不会重置(比如email1目录是
part-65432-1.txt、part-65432-2.txt,email2目录直接从part-65432-3.txt开始),期望每个子目录的编号从1开始
已知MIME::Parser会创建MIME::Parser::Filer实例,该类通过词法变量$GFileNo跟踪文件编号。尝试过为每封邮件新建MIME::Parser实例、调用output_under(),但$GFileNo并未重置;尝试子类化MIME::Parser::Filer添加reset_numbering方法,也未生效。
当前代码:
sub parsemail { my $file=shift(@_); my $base=$file; $base =~ s|^.*/||; $base =~ s|.emlx$||; $outdir="$ROOTDIR/$base"; mkdir("$outdir") || die("$!"); my $parser = new MIME::Parser; $parser->output_dir("$outdir"); $parser->output_prefix("part"); my $raw=""; open(IN,$file); <IN>; #Eat the length line Apple adds at top of emlx while(<IN>) { $raw .= $_; } close(IN); my $mimeparsed=$parser->parse_data($raw); }
请教:正确的类覆写方式是什么?是否有直接修改MIME::Parser::Filer中$GFileNo的方法?是否有其他简便修复方案?
解决方案
方法1:直接重置全局变量(快速实现)
$GFileNo是MIME::Parser::Filer中的词法变量,可通过Perl符号表操作在处理每封邮件前重置:
# 在创建新parser前重置全局编号 { no strict 'refs'; ${'MIME::Parser::Filer::GFileNo'} = 0; } my $parser = MIME::Parser->new;
同时,若要去掉文件名中的进程ID,可后续通过字符串替换处理生成的文件名,或结合其他方法自定义命名规则。
方法2:正确子类化MIME::Parser::Filer
之前子类化未生效,是因为未替换parser使用的默认Filer实例,正确步骤如下:
- 定义自定义Filer类,重置编号并修改命名规则:
package My::Filer; use base 'MIME::Parser::Filer'; sub new { my ($class, %args) = @_; my $self = $class->SUPER::new(%args); # 重置全局部件编号 ${'MIME::Parser::Filer::GFileNo'} = 0; return $self; } # 自定义无命名部件文件名,移除PID sub make_filename { my ($self, $head) = @_; my $name = $self->SUPER::make_filename($head); # 替换掉文件名中的PID部分,比如 part-65432-1.txt → part-1.txt $name =~ s/-[0-9]+-(?=\d+\.\w+$)/-/; return $name; }
- 在parser中替换为自定义Filer:
my $parser = MIME::Parser->new; my $filer = My::Filer->new( OutputDir => $outdir, OutputPrefix => 'part', ); $parser->filer($filer);
方法3:使用output_filename回调(最灵活简便)
MIME::Parser支持通过output_filename回调完全自定义文件名,无需修改Filer类:
sub parsemail { my $file=shift(@_); my $base=$file; $base =~ s|^.*/||; $base =~ s|.emlx$||; my $outdir="$ROOTDIR/$base"; mkdir("$outdir") || die("$!"); my $parser = MIME::Parser->new; $parser->output_dir($outdir); # 每封邮件独立维护部件编号 my $part_num = 1; $parser->output_filename(sub { my ($parser, $head, $body) = @_; # 优先使用邮件部件自带的文件名 my $filename = $head->recommended_filename; if ($filename) { # 处理重复文件名,避免覆盖 my $ext = $filename =~ s/(\.\w+)$// ? $1 : ''; my $final_name = $filename; while (-f "$outdir/$final_name$ext") { $final_name .= "_$part_num"; } return "$final_name$ext"; } else { # 无命名部件使用part-N格式,从1开始递增 my $name = "part-$part_num.txt"; $part_num++; return $name; } }); my $raw=""; open(IN,$file); <IN>; # 跳过emlx开头的长度行 while(<IN>) { $raw .= $_; } close(IN); my $mimeparsed=$parser->parse_data($raw); }
该方法通过局部变量$part_num实现每封邮件独立编号,同时完全控制文件名生成逻辑,彻底解决PID和全局编号问题。
内容的提问来源于stack exchange,提问作者thomasafine
相关产品推荐
相关产品推荐

