【问题标题】:Cleanest Perl parser for Makefile-like continuation lines用于类似 Makefile 的续行的最干净的 Perl 解析器
【发布时间】:2010-11-03 12:45:57
【问题描述】:

我正在编写的 perl 脚本需要解析具有延续行的文件,例如 Makefile。即以空格开头的行是前一行的一部分。

我写了下面的代码,但不觉得它很干净或 perl-ish(见鬼,它甚至没有使用“重做”!)

有许多边缘情况:奇数位置的 EOF、单行文件、以空行(或非空行或续行)开头或结尾的文件、空文件。我所有的测试用例(和代码)都在这里:http://whatexit.org/tal/flatten.tar

你能写出通过我所有测试的更简洁、perl-ish 的代码吗?

#!/usr/bin/perl -w

use strict;

sub process_file_with_continuations {
    my $processref = shift @_;
    my $nextline;
    my $line = <ARGV>;

    $line = '' unless defined $line;
    chomp $line;

    while (defined($nextline = <ARGV>)) {
        chomp $nextline;
        next if $nextline =~ /^\s*#/;  # skip comments
        $nextline =~ s/\s+$//g;  # remove trailing whitespace
        if (eof()) {  # Handle EOF
            $nextline =~ s/^\s+/ /;
            if ($nextline =~ /^\s+/) {  # indented line
                &$processref($line . $nextline);
            }
            else {
                &$processref($line);
                &$processref($nextline) if $nextline ne '';
            }
            $line = '';
        }
        elsif ($nextline eq '') {  # blank line
            &$processref($line);
            $line = '';
        }
        elsif ($nextline =~ /^\s+/) {  # indented line
            $nextline =~ s/^\s+/ /;
            $line .= $nextline;
        }
        else {  # non-indented line
            &$processref($line) unless $line eq '';
            $line = $nextline;
        }
    }
    &$processref($line) unless $line eq '';
}

sub process_one_line {
    my $line = shift @_;
    print "$line\n";
}

process_file_with_continuations \&process_one_line;

【问题讨论】:

    标签: perl fileparsing redo


    【解决方案1】:

    如果您不介意将整个文件加载到内存中,那么下面的代码将通过测试。 它将行存储在一个数组中,将每一行添加到前一行(续行)或数组末尾(其他)。

    #!/usr/bin/perl
    
    use strict;
    use warnings;
    
    my @out;
    
    while( <>)
      { chomp;
        s{#.*}{};             # suppress comments
        next unless( m{\S});  # skip blank lines
        if( s{^\s+}{ })       # does the line start with spaces?
          { $out[-1] .= $_; } # yes, continuation, add to last line
        else 
          { push @out, $_;  } # no, add as new line
      }
    
    $, = "\n";                # set output field separator
    $\ = "\n";                # set output record separator
    print @out;          
    

    【讨论】:

    • 如果您只是想一一处理(连接的)行,您的算法当然也可以工作。只需进行处理(或打印),而不是推送到@out。这样就不需要一次将整个文件放在内存中。
    • @blixtor:确实可以用 $last_line 替换 @out,用 if( s{^\s+}{ }) { $last_line.= $_; 更改内部 if } else { 打印 $last_line, "\n"; $last_line= $_; } 和最后 3 行 print $last_line, "\n" if $last_line.我假设 Makefile 类型的行不会太大而无法放入内存。
    • 是的,我宁愿这样做而不将所有内容都读入内存。这些文件可能很大!
    【解决方案2】:

    如何将整个文件放入内存并使用正则表达式进行处理。更多的'perlish'。这通过了您的测试,并且 更小更整洁:

    #!/usr/bin/perl
    
    use strict;
    use warnings;
    
    $/ = undef;             # we want no input record separator.
    my $file = <>;          # slurp whole file
    
    $file =~ s/^\n//;       # Remove newline at start of file
    $file =~ s/\s+\n/\n/g;  # Remove trailing whitespace.
    $file =~ s/\n\s*#[^\n]+//g;     # Remove comments.
    $file =~ s/\n\s+/ /g;   # Merge continuations
    
    # Done
    print $file;
    

    【讨论】:

    • 我和 Mirod 的回答要记住的一点是,如果您将其嵌入到更大的代码段中(例如,'local $ /')
    • @mirod - 呵呵。这与我教授的 Perl 介绍课程中的一个练习几乎相同(它是关于展开邮件标题)。这可能是一个相当普遍的问题,so 有很多方法可以做到这一点:)
    猜你喜欢
    • 2012-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多