【问题标题】:Perl - Removing multiple lines from file with multiple regexPerl - 使用多个正则表达式从文件中删除多行
【发布时间】:2015-08-31 20:45:00
【问题描述】:

我(显然)是 Perl 新手,我正在尝试创建一个简单的脚本来每周清理大约 450 万条记录的大文件。我想完全删除与三种模式之一匹配的行。该文件如下所示:

D0832
G2565
ZDS97
FHM2547
JDH1464
R2918
4918K
AG01023
AG02997

我的下面的脚本有效,但是我在发生删除(替换)的地方得到一个空白行,而不是完全删除该行。

#!/usr/bin/perl

open( FH, "serial.txt" ) || die "Couldn't open file...\n";

while ( <FH> ) {
   $data .= $_;
}

$data =~ s/[A][F|G][(0-9)]{5}//g;
$data =~ s/[A-Z][0-9][0-9][0-9][0-9]//g;
$data =~ s/[0-9][0-9][0-9][0-9][A-Z]//g;

print $data;
close( FH );

我的问题是 - 有 450 万条记录,每周至少运行一次,这是一种高效/快速的方式来完成我想做的事情,还是有更有效的方式来做到这一点?另外,我怎样才能删除这些行而不是替换一个空行?

谢谢大家。斯蒂芬

【问题讨论】:

  • 关于如何删除这些行 - 在查找正则表达式的末尾包含 \n。关于它是否足够快 - 它肯定会在一周的时间内通过,但你必须测试它是否让自己满意:)
  • [(0-9)] 也匹配 ()。同样,[F|G] 也匹配 |
  • 你有没有考虑过完全不使用 perl 来解决这个问题。我认为grep -v 'regexp' 会做得更好。请参阅 grep(1) 实用程序手册页中的选项 -v。 Grep 擅长过滤文本行。它是在考虑到该目标的情况下开发的。并且至少比 perl 大十岁。

标签: regex perl


【解决方案1】:

@ndn 的评论是正确的。但是,就个人而言,我不会阅读整个文件,而是逐行处理它(我也冒昧地整理了你的正则表达式):

#!/usr/bin/perl -p
$_ = '' if /^A[FG]\d{5}$/ || /^[A-Z]\d{4}$/ || /^\d{4}[A-Z]$/;

#!/usr/bin/perl -n
print unless /^A[FG]\d{5}$/ || /^[A-Z]\d{4}$/ || /^\d{4}[A-Z]$/;

(在这两种情况下,请在命令行中指定您的输入文件。阅读perlrun 手册页,了解-p-n 选项的工作原理。)

【讨论】:

  • 所有这些建议都很棒!大进步,谢谢。我的正则表达式只有一个问题 - 这是过滤我想保留的 FHM2547 和 JDH1464。我只想删除完全匹配的行而不是一部分。我会使用 ^ 和 $ 吗?
  • @StephenDundas 是的,用^$ 锚定所有正则表达式。我将编辑我的答案以合并。
【解决方案2】:

首先,我会列出一个预编译模式列表来测试每一行。问题可能会改变,我想添加和删除模式而不影响代码的内容:

my @patterns = ( 
    qr/\A [A] [FG]  [0-9]{5} \Z/x,
    qr/\A [A-Z]     [0-9]{4} \Z/x,
    qr/\A [0-9]{4}  [A-Z]    \Z/x,
    );

while( my $line = <DATA> ) {
    next if grep { $line =~ $_ } @patterns;

    print $line;
    }

__END__
D0832
G2565
ZDS97
FHM2547
JDH1464
R2918
4918K
AG01023
AG02997

最大的改进不是模式。它一次检查一行并打印我想要保留的行。我没有同时将整个文件保存在内存中;一次只有一行。

这有一个问题。它可以工作,但它每次都会检查每个模式。如果只有很少的行匹配或只有少数模式,那可能没有多大意义。如果您认为这可能很重要,使用 List::Util 中的 first 而不是 grep 会有所帮助,因为它只需要找到一个匹配项并在找到它时停止:

use List::Util qw(first);

my @patterns = ( 
    qr/\A [A] [FG]  [0-9]{5} \Z/x,
    qr/\A [A-Z]     [0-9]{4} \Z/x,
    qr/\A [0-9]{4}  [A-Z]    \Z/x,
    );

while( my $line = <DATA> ) {
    next if first { $line =~ $_ } @patterns;

    print $line;
    }

__END__
D0832
G2565
ZDS97
FHM2547
JDH1464
R2918
4918K
AG01023
AG02997

或者,我可以制作一个巨大的图案。 Regexp::Assemble 可以将它们放在一起(但如果您注意交替优先级,您也可以):

use v5.10;

use Regexp::Assemble;

my @patterns = ( 
    '[A][FG][0-9]{5}',
    '[A-Z][0-9]{4}',
    '[0-9]{4}[A-Z]',
    );

my $grand_pattern = do {
    my $ra = Regexp::Assemble->new;
    $ra->add( $_ ) for @patterns;
    my $re = $ra->re;
    qr/ \A (?: $re ) \Z /x;
    };

say "Grand regex is $grand_pattern";

while( my $line = <DATA> ) {
    next if $line =~ $grand_pattern;

    print $line;
    }

__END__
D0832
G2565
ZDS97
FHM2547
JDH1464
R2918
4918K
AG01023
AG02997

下一步是从命令行或配置文件中获取模式,但这并不难。该程序根本不应该知道这些模式。如果您不必更改代码,那么更改模式会容易得多。

【讨论】:

    【解决方案3】:

    不需要多个正则表达式模式。这将满足您的需要

    perl -ne'print unless /^(?:[A][FG]\d{5}|[A-Z]\d{4}|\d{4}[A-Z])$/' serial.txt
    

    输出

    ZDS97
    FHM2547
    JDH1464
    

    【讨论】:

      【解决方案4】:
       $data =~ s/[A-Z][0-9][0-9][0-9][0-9][\s\r\n]*//g;
       $data =~ s/[0-9][0-9][0-9][0-9][A-Z][\s\r\n]*//g;
      

      来自问题:

      “我怎样才能删除这些行而不是替换一个空行?”

      每个正则表达式的结尾,我们可以有一个换行符/返回。然后正则表达式将替换空行。因此我添加了 [\s\r\n]* 语法,它不会替换空行。

      【讨论】:

      • 请补充说明
      猜你喜欢
      • 1970-01-01
      • 2015-02-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多