【问题标题】:Parsing data from delimited blocks从分隔块解析数据
【发布时间】:2019-01-27 13:20:55
【问题描述】:

我有一个日志文件内容很多块/begin CHECK ... /end CHECK 如下所示:

/begin CHECK

Var_AAA
"Description AAA"
DATATYPE UBYTE
Max_Value 255.
ADDRESS 0xFF0011

/end CHECK

/begin CHECK

Var_BBB
"Description BBB"
DATATYPE UBYTE
Max_Value 255.
ADDRESS 0xFF0022

/end CHECK
...

我想提取变量名及其地址,然后像这样写入一个新文件

Name    Address
Var_AAA => 0xFF0011
Var_BBB => 0xFF0022

我只是在考虑($start, $keyword, $end) 来检查每个块并仅在关键字之后提取数据

#!/usr/bin/perl

use strict;
use warnings;

my $input  = 'input.log';
my $output = 'output.out';

my ( $start, $keyword, $end ) = ( '^\/begin CHECK\n\n', 'ADDRESS ', '\/end CHECK' );
my @block;

# open input file for reading
open( my $in, '<', $input ) or die "Cannot open file '$input' for reading: $!";

# open destination file for writing
open( my $out, '>', $output ) or die "Cannot open file '$output' for writing: $!";

print( "copying variable name and it's address from $input to $output \n" );

while ( $in ) {    #For each line of input

    if ( /$start/i .. /$end/i ) {    #Block matching
        push @block, $_;
    }

    if ( /$end/i ) {

        for ( @block ) {

            if ( /\s+ $keyword/ ) {
                print $out join( '', @block );
                last;
            }
        }

        @block = ();
    }

    close $in or die "Cannot close file '$input': $!";
}

close $out or die "Cannot close file '$output': $!";

但我在处决后一无所获。任何人都可以建议我的示例想法吗?

【问题讨论】:

  • 对不起先生,我明白你的意思了,我只是一步一步生成地址而已。

标签: string perl parsing


【解决方案1】:

大多数东西看起来都不错,但导致第一个问题的是您的启动正则表达式:

'^\/begin CHECK\n\n'

您正在从文件中读取行,然后连续查找两个换行符。这永远不会匹配,因为一行恰好以一个换行符结尾(除非您更改 $/,但这是一个不同的主题)。如果要匹配线路的发送,可以使用$(或\z)锚点:

'^\/begin CHECK$'

这是我精简的程序。您可以对其进行调整以完成您需要做的所有其他事情:

use v5.10;
use strict;
use warnings;

use Data::Dumper;

my ($start, $keyword, $end) = (qr{^/begin CHECK$}, qr(^ADDRESS ), qr(^/end CHECK));

while (<DATA>) #For each line of input
{
    state @block;
    chomp;
    if (/$start/i .. /$end/i) #Block matching
    {
        push @block, $_ unless /^\s*$/;
    }

    if( /$end/i )
    {
        print Dumper( \@block );
        @block = ();
    }
}

之后,您将不再读取数据。您需要将文件句柄放在&lt;&gt;(行输入运算符)中:

 while ( <$in> )

文件句柄将在程序结束时自动关闭。如果您想自己关闭它们,那很好,但在完成之前不要这样做。在while 完成之前不要关闭$in

【讨论】:

  • 另外:while ($in) 代替 while (&lt;$in&gt;)close $in inside while 循环。
【解决方案2】:

在 Windows 中使用命令提示符。在 MacOS 或 Unix 中将遵循您可以执行的相同逻辑:

 perl -wpe "$/='/end CHECK';s/^.*?(Var_\S+).*?(ADDRESS \S+).*$/$1 => $2\n/s" "your_file.txt">"new.txt

首先我们将 endLine 字符设置为$/ = "/end CHECK".

然后我们只选择第一个Var_ 和第一个ADDRESS.,同时在单行模式下删除其他所有内容,即点匹配换行符\n。 s/^.*?(Var_\S+).*?(ADDRESS \S+).*$/$1 =&gt; $2\n/s.

然后我们将结果写入一个新文件。即>新文件。

确保使用 -w -p -e 其中 -e 用于执行代码,-p 用于打印,-w 用于警告:

在这段代码中,我没有将值写入新文件,即没有包含&gt;newfile.txt prt,以便您可以看到结果。如果您确实包含该部分,只需打开newfile.txt,所有内容都将打印在那里

【讨论】:

    【解决方案3】:

    以下是您的代码存在的一些问题

    • 你有while ($in)而不是while ( &lt;$in&gt; ),所以你的程序永远不会从输入文件中读取

    • close你的输入文件句柄while读取循环,所以你只能读取一条记录

    • 您的$start 正则表达式模式是'^\/begin CHECK\n\n'。单引号使您的程序搜索 backslash n backslash n 而不是 newline newline

    • 您的测试if (/\s+ $keyword/) 查找多个任意类型的空格字符,后跟一个空格,然后是ADDRESS——$keyword 的内容。在您的数据中任何地方都不会出现ADDRESS 前面有空格

    你也写了太多 没有测试任何东西。您应该首先编写自己的读取循环,并确保数据正确输入,然后在测试之间一次添加两到三行代码。在测试之前编写 90% 的功能是一种非常糟糕的方法。

    将来,为了帮助您解决此类问题,我会向您指出 Stack Overflow 上链接的优秀资源Perl tag information page

    这里唯一有点模糊的是范围运算符 /$start/i .. /$end/i 返回一个有用的值;我已将其复制到$status。运算符第一次匹配,结果为 1;第二次 2 等等。最后一次是不同的,因为它是一个使用工程符号的字符串,如 9E0,所以它仍然计算为正确的计数,但您可以使用 /E/ 检查最后一次匹配。我使用了== 1/E/ 来避免将开始行和结束行推到@block

    我认为这里没有其他您在Perl language reference 中找不到的过于复杂的东西

    use strict;
    use warnings;
    use autodie;  # Handle bad IO status automatically
    
    use List::Util 'max';
    
    my ($input, $output) = qw/ input.log output.txt /;
    
    open my $in_fh,  '<', $input;
    
    my ( @block, @vars );
    
    while ( <$in_fh> ) {
    
        my $status = m{^/begin CHECK}i .. m{^/end CHECK}i;
    
        if ( $status =~ /E/ ) { # End line
    
            @block = grep /\S/, @block;
            chomp @block;
    
            my $var = $block[0];
            my $addr;
            for ( @block ) {
                if ( /^ADDRESS\s+(0x\w+)/ ) {
                    $addr = $1;
                    last;
                }
            }
    
            push @vars, [ $var, $addr ];
    
            @block = ();
        }
        elsif ( $status ) {
            push @block, $_ unless $status == 1;
        }
    }
    
    # Format and generate the output
    
    open my $out_fh, '>', $output;
    
    my $w = max map { length $_->[0] } @vars;
    printf $out_fh "%-*s => %s\n", $w, @$_ for [qw/ Name Address / ], @vars;
    
    close $out_fh;
    

    输出

    Name    => Address
    Var_AAA => 0xFF0011
    Var_BBB => 0xFF0022
    



    更新

    为了它的价值,我会写这样的东西。它产生与上面相同的输出

    use strict;
    use warnings;
    use autodie;  # Handle bad IO status automatically
    
    use List::Util 'max';
    
    my ($input, $output) = qw/ input.log output.txt /;
    
    my $data = do {
        open my $in_fh, '<', $input;
        local $/;
        <$in_fh>;
    };
    
    my @vars;
    
    while ( $data =~ m{^/begin CHECK$(.+?)^/end CHECK$}gms ) {
        my $block = $1;
        next unless $block =~ m{(\w+).+?ADDRESS\s+(0x\w+)}ms;
        push @vars, [ $1, $2 ];
    }
    
    open my $out_fh, '>', $output;
    
    my $w = max map { length $_->[0] } @vars;
    printf $out_fh "%-*s => %s\n", $w, @$_ for [qw/ Name Address / ], @vars;
    
    close $out_fh;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多