【问题标题】:Parsing irregular text files in Perl在 Perl 中解析不规则的文本文件
【发布时间】:2011-08-29 05:25:41
【问题描述】:

我是 perl 编程的新手,想了解如何使用 perl 解析文本文件。 我有一个包含不规则格式的文本文件,我想将其解析为三个。

基本上,该文件包含类似于以下的文本:

;out;asoljefsaiouerfas'pozsirt'z
mysql_query("SELECT * FROM Table WHERE (value='true') OR (value2='true') OR (value3='true') ");
1234 434 3454

4if[9put[e]9sd=09q]024s-q]3-=04i
select ta.somefield, tc.somefield 
from TableA ta INNER JOIN TableC tc on tc.somefield=ta.somefield 
INNER JOIN TableB tb on tb.somefield=ta.somefield 
ORDER by tb.somefield
234 4536 234

列表继续使用这种格式。

所以我需要做的就是将其解析为三个。即最上面的那个,得到哈希检查。第二个是 mysql 查询,第三个是解析三个数字。出于某种原因,我不知道如何做到这一点。我使用 perl 中的 'open' 函数从文本文件中获取数据。然后我尝试对换行符使用“拆分”功能,但结果发现查询不在单行或模式中,所以我不能像我想的那样使用它。

【问题讨论】:

  • 有人“设计”了这种文件格式 - 我建议你与他签订合同。
  • 我认为,您的部分问题是想象您需要在解析之前读取整个文件。 Perl 有很好的工具来帮助您处理逐行处理,您可以使用这些工具来解决这个问题。 :-)

标签: perl parsing text


【解决方案1】:

假设:

  1. 数据块之间会有一个空行。
  2. 该空行将仅包含一个换行符。
  3. 在这些块中,哈希检查将是顶部 single 行,三个数字将是底部 single 行。

考虑到这一点:

use strict;
use warnings;
use English qw<$RS $OS_ERROR>;

local $RS = "\n\n";

open( my $fh, '<', $path_to_file ) 
    or die "Could not open $path_to_file! - $OS_ERROR"
    ;
while ( <> ) { 
    chomp;
    my ( $hash_check_line
       , @inner_lines 
       )
       = split /\n/
       ;
    my @numbers = split /\D+/, pop @inner_lines;
    my $sql     = join( "\n", @inner_lines );

    ...
}

通过将 $RS$/$INPUT_RECORD_SEPARATOR)更改为双换行符,我们改变了读取记录的方式。

这并不奇怪,但是在我使用 Perl 的这些年里,我不得不为记录分隔符制作一些非常有趣的字符串,但有时只需读取您想要读取的块即可。

【讨论】:

  • +1:有时您只需从更改输入记录分隔符开始,除非您想处理完全疯狂。 :-)
  • 一开始我对更改 $RS 的想法持反对态度(因为如果可能的话,我讨厌更改这些变量),但是……嗯,它是一个记录分隔符。我感觉合理。 :)
  • 如果有的话,我会对 $RS local 进行更改,这样它就不会影响您的整个程序。 :)
【解决方案2】:

哦,天哪。

我看到的算法是:

  • 缓存第一行。
  • 读取所有行,直到出现空行。
  • “最后”行将是数字。
  • 剩下的都是查询。

考虑到这一点,我提供以下代码:

open my $fh, '<', $path_to_file
    or die "Can't open $path_to_file: $!";
while (my ($checksum, $query, $numbers) = read_record($fh) ) {
    # do something with record
}
close $fh or warn "$!";

sub read_record {
    my $fh = shift;
    my @lines;
    LINE: while (my $line = <$fh>) {
        chomp $line;
        last LINE if $line eq q{}; # if empty, we're done with the record!
        push @lines, $line;        # store it :)
    }
    return unless @lines;          # if we didn't get anything, eof!
    my $checksum = shift @lines;   # first was checksum.
    my $numbers = pop @lines;      # last thing read was numbers.
    my $query = join ' ', @lines;  # everything else, query.
    return ($checksum, $query, $numbers);
}

当然要修改以适应边界条件。

【讨论】:

    【解决方案3】:

    以下似乎有效:

    while ($file_content =~ /\s*^(.+?)^(.*?)^(\d+\s+\d+\s+\d+)$/smg) {
        my $checksum = $1;
        my $query = $2;
        my $numbers = $3;
        # do stuff
    }
    

    这里是对正则表达式的解释:

    \s*                   # eat up empty lines
    ^(.+?)                # save the checksum line to group 1
    ^(.+?)                # save one or multiple query lines to group 2
    ^(\d+\s+\d+\s+\d+)$   # save number line to group 3
    

    第一组总是只有一行,因为当遇到下一行时它是惰性的,正则表达式将尝试从第二组开始匹配。到那时,如果可以完成剩下的比赛,那么第二组将包含数字之前的所有后续行。

    【讨论】:

    • 什么鬼?我看不懂那个正则表达式...我可以建议你qr 使用/x 模式的正则表达式并提供良好的空格和cmets? (我也不完全是 Perl 新手。我知道正则表达式有时很难阅读。但为了清晰起见,我提供了 cmets,当我需要在路上维护它时它会有所帮助!)
    • @Platinum Azure 现在正在开发一个评论版本,只是想先得到答案。
    • 另外,这将是。不过,通过在第二个捕获组中使用贪婪匹配,您可能会获得少量性能提升。
    • 如果第二个捕获组是贪婪的,那么它将匹配到最后一行三位数。我知道这不像其他解决方案那样有效,但它也没有对文件包含的内容做出任何假设,因此如果有文件块不在校验和中,OP 可能会发现它很有用,查询,数字格式。
    • 它不会匹配到最后一行,因为下一个捕获组不会捕获任何内容并且匹配失败,因此正则表达式引擎需要回溯一行。比在第二组中只获得一行,然后看到第二行(假设是 SQL 中 3 行中的第 2 行)与数字模式不匹配,然后“向前跟踪”(如果你愿意) 获取 2 行 SQL,看到第三行与数字不匹配,“向前跟踪”最终获取所有三行...想象一下如果您有 10 行或 20 行或 100 行 SQL 会发生什么声明!
    猜你喜欢
    • 1970-01-01
    • 2012-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-04
    • 2023-03-15
    • 2011-01-03
    • 2012-10-25
    相关资源
    最近更新 更多