【问题标题】:Perl - Regexp to manipulate .csvPerl - 正则表达式来操作 .csv
【发布时间】:2014-12-04 12:50:41
【问题描述】:

我在 Perl 中有一个函数,它读取文件夹中最后修改的 .csv,并将其值解析为变量。

我发现正则表达式存在一些问题。 我的 .csv 看起来像:

Title is: "NAME_NAME_NAME"
"Period end","Duration","Sample","Corner","Line","PDP OUT TOTAL","PDP OUT OK","PDP OUT NOK","PDP OUT OK Rate"
"04/12/2014 11:00:00","3600","1","GPRS_OUT","ARG - NAME 1","536","536","0","100%"
"04/12/2014 11:00:00","3600","1","GPRS_OUT","USA - NAME 2","1850","1438","412","77.72%"
"04/12/2014 11:00:00","3600","1","GPRS_OUT","AUS - NAME 3","8","6","2","75%"


.(ignore this dot, you will understand later)

到目前为止,我已经通过以下方式帮助将值解析为一些变量:

open my $file, "<", $newest_file
        or die qq(Cannot open file "$newest_file" for reading.);
while ( my $line = <$file> ) {

    my ($date_time, $duration, $sample, $corner, $country_name, $pdp_in_total, $pdp_in_ok, $pdp_in_not_ok, $pdp_in_ok_rate) 
            = parse_line ',', 0, $line;

    my ($date, $time) = split /\s+/, $date_time;
    my ($country, $name) = $country_name =~ m/(.+) - (.*)/;

    print "$date, $time, $country, $name, $pdp_in_total, $pdp_in_ok_rate";
}

问题是:

  1. 我不知道如何使第一行和第二行(即 .csv 中的列名)被忽略;
  2. 该文件有时在文件末尾带有 2-5 个空行,如我在示例中所示(忽略它末尾的点,它在文件中不存在)。

我该怎么做?

【问题讨论】:

  • 您是否尝试使用 Perl 解析 CSV?或者您是否正在尝试使用 Perl 学习正则表达式?
  • 其实我两者都做。我需要以一种或另一种方式学习它

标签: regex perl csv


【解决方案1】:

当您有一个带有列标题的 csv 文件并希望将数据解析为变量时,最简单的选择是使用 Text::CSV。此代码显示了如何将数据放入散列引用 $row。 (即my %data = %$row

use strict;
use warnings;
use Text::CSV;
use feature 'say';

my $csv = Text::CSV->new({
        binary  => 1,
        eol => $/,
    });
# open the file, I use the DATA internal file handle here
my $title = <DATA>;

# Set the headers using the header line
$csv->column_names( $csv->getline(*DATA) );

while (my $row = $csv->getline_hr(*DATA)) {
    # you can now access the variables via their header names, e.g.:
    if (defined $row->{Duration}) {  # this will skip the blank lines
        say $row->{Duration};
    }
}

__DATA__
Title is: "NAME_NAME_NAME"    
"Period end","Duration","Sample","Corner","Line","PDP IN TOTAL","PDP IN OK","PDP IN NOT OK","PDP IN OK Rate"
"04/12/2014 10:00:00","3600","1","GRPS_INB","CHN - Name 1","1198","1195","3","99.74%"
"04/12/2014 10:00:00","3600","1","GRPS_INB","ARG - Name 2","1198","1069","129","89.23%"
"04/12/2014 10:00:00","3600","1","GRPS_INB","NLD - Name 3","813","798","15","98.15%"

如果我们用Data::Dumper 打印$row 变量之一,它会显示我们从Text::CSV 返回的结构:

$VAR1 = {
          'PDP IN TOTAL' => '1198',
          'PDP IN NOT OK' => '3',
          'PDP IN OK' => '1195',
          'Period end' => '04/12/2014 10:00:00',
          'Line' => 'CHN - Name 1',
          'Duration' => '3600',
          'Sample' => '1',
          'PDP IN OK Rate' => '99.74%',
          'Corner' => 'GRPS_INB'
        };

【讨论】:

    【解决方案2】:
    open ...
    my $names_from_first_line = <$file>; # you can use them or just ignore them
    
    while($my line = <$file>) {
        unless ($line =~ /\S/) {
            # skip empty lines
            next;
        }
    ..
    }
    

    另外,考虑使用 Text::CSV 处理 CSV 格式

    【讨论】:

    • OP 使用的是Text::ParseWords,它与Text::CVS 做类似的工作,但它是一个标准的 Perl 模块。
    【解决方案3】:

    1) 我不知道如何让第一行(即 .csv 中的列名)被忽略;

    while ( my $line = <$file> ) {
        chomp $line;
        next  if $. == 1 || $. == 2;
    

    2) 该文件有时在文件末尾带有 2-5 个空行,如我在示例中所示(忽略它末尾的点,它在文件中不存在)。

    while ( my $line = <$file> ) {
        chomp $line;
        next if $. == 1 || $. == 2;
        next if $line =~ /^\s*$/;
    

    【讨论】:

    • 我错过了第一行,朋友。对不起。我编辑了帖子
    • 它仍然捕获空白值...当我打印时,我收到空白空格:/
    • 应该是chomp $line; 而不仅仅是chomp。有关详细信息,请参阅 [chomp](http;//perldoc.perl.org/functions/chomp.html) 上的 Perldoc。顺便说一句,perldoc.perl.org 页面是关于 Perl 的完整文档,您甚至可以在左上角的弹出框中选择版本以匹配您的版本。
    • 我认为您的意思是 \s* 而不是 s*
    【解决方案4】:

    您知道有效行将以日期开头。我建议您直接跳过不以您期望的格式开始日期的行:

    while ( my $line = <$file> ) {
        warn qq(next if not $line =~ /^"\d{2}-\d{2}-d{4}/;);  # Temp debugging line
        next if not $line =~ /^"\d{2}-\d{2}-d{4}/;
        warn qq($line matched regular expression);            # Temp debugging line
        ...
    }
    

    /^"\d{2}-\d{2}-d{4}",/regular expression pattern。模式在/.../之间:

    • ^ - 行首。
    • " - 引号。
    • \d{2} - 后跟两位数字。
    • - - 后跟一个破折号。
    • \d{2] - 后跟两位数字。
    • - - 后跟一个破折号。
    • \d{4} - 后跟四位数字

    这应该描述您的行的第一部分,即MM-DD-YYYY 格式的日期,用引号括起来,后跟逗号。 =~ 告诉 Perl 你希望左边的东西匹配右边的正则表达式。

    正则表达式可能难以理解,这也是 Perl 作为一种只写语言而享有盛誉的原因之一。正则表达式被比作 sailor cussing。然而,正则表达式是一个非常强大的工具,值得努力学习。并且有了一些经验,您将能够轻松解码它们。

    next if... 语法类似于:

    if (...) {
        next;
    }
    

    通常,您不应该使用后缀if 并且永远不要使用unless(这是if 的反面)。它们会使您的程序更难理解。但是,当像这样放置在循环的开头行之后时,它们会明确声明您正在过滤掉不需要的行。我本可以这样写的(很多人会认为这样更可取):

     next unless $line =~ /^"\d{2}-\d{2}-d{4}",/;
    

    这是说你想跳过行,除非它们与你的正则表达式匹配。这完全是个人喜好问题,你认为对于明年出现的可怜的 schlub 来说更容易,并且必须弄清楚你的程序在做什么。

    我实际上考虑了这一点,并决定 if not ... 说我希望文件中的几乎所有行都符合我的格式,并且我想抛弃少数例外。对我来说,next unless ... 是说有 一些 行与我的正则表达式匹配,而许多行不匹配,我只想处理匹配的行。

    这使我们进入了编程的下一部分:注意会破坏您的程序的事情。我之前的回答没有做很多错误检查,但它应该。如果一行与您的格式不匹配会怎样?如果split 不起作用怎么办?如果字段不是我所期望的怎么办?您应该真正检查每个语句以确保它确实有效。 Perl 中的几乎所有函数都将返回零、空字符串或 undef(如果它们不工作)。例如,open 语句。

    open my $file, "<", $newest_file
        or die qq(Cannot open file "$newest_file" for reading.);
    

    如果open 不起作用,它会返回一个文件句柄值为零。 or 声明如果 open 没有返回非零文件句柄,则执行后面的行,这会杀死您的程序。

    因此,请查看您的程序,并查看您假设某事按预期工作的任何地方,并考虑如果没有,会发生什么情况。然后,如果您遇到该异常,请在您的程序中添加检查。可能是您想要报告错误或记录错误并跳到下一行。可能是您希望您的程序突然停止。可能是您可以从错误中恢复并继续。无论您做什么,都要检查可能的错误(尤其是来自用户输入)并处理可能的错误。


    调试

    我告诉过你正则表达式很棘手。是的,假设您的日期是一个单独的字段,我犯了一个错误。相反,它后面是一个空格,然后是时间,这意味着正则表达式中的最后一个 ", 不应该在那里。我已经修复了上面的代码。但是,您可能仍需要测试和调整。这让我们开始在 Perl 中进行调试。

    您可以使用warn 语句来帮助调试您的程序。如果你复制一个语句,然后用warn qq(...); 包围它,Perl 将打印出行(填写变量)和行号。我什至在我的各种编辑器中创建宏来为我做这件事。

    qq(...)quote like operator。这是另一种在字符串周围加双引号的方法。好消息是字符串可以包含实际的引号,qq(...); 仍然可以使用。

    完成调试后,您可以搜索 warn 语句并将其删除。 Perl 带有强大的built in debugger,许多 IDE 都与它集成。但是,有时添加一些warn 语句会更容易查看代码中发生了什么——尤其是当您遇到正则表达式问题时。

    【讨论】:

    • 伙计,多好的答案啊。非常感谢!
    • 但是我必须说:当程序进入while循环时,你用这个正则表达式让他等待一个日期,但是/^"\d{2}-\d{2}-d{4}",/;与YY后面的小时不匹配, 可以?当我运行程序时,它根本不打印任何内容。我想应该是/^"\d{2}-\d{2}-d{4} \d{2}:\d{2}:\d{2}",/;,对吧?
    • 不打印 while 循环内的任何内容*
    • 哦,对了,年份后面没有引号和逗号。我只是针对年份进行测试,因为它足以拒绝标题行或空白行。我会修复我的帖子。
    • 我已经编辑了我的答案以修复正则表达式。另外,我添加了如何使用warn 语句进行调试。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-22
    • 1970-01-01
    • 1970-01-01
    • 2013-10-07
    相关资源
    最近更新 更多