如何使用正则表达式解析 Perl 中引用的 CSV?

Posted

技术标签:

【中文标题】如何使用正则表达式解析 Perl 中引用的 CSV?【英文标题】:How can I parse quoted CSV in Perl with a regex? 【发布时间】:2010-10-12 15:03:24 【问题描述】:

我在解析带引号的 CSV 数据时遇到了一些问题。我的主要问题是字段中的引号。在以下示例中,第 1 - 4 行正常工作,但第 5,6 和 7 行不正常。

COLLOQ_TYPE,COLLOQ_NAME,COLLOQ_CODE,XDATA
S,"BELT,FAN",003541547,
S,"BELT V,FAN",000324244,
S,SHROUD SPRING SCREW,000868265,
S,"D" REL VALVE ASSY,000771881,
S,"YBELT,"V"",000323030,
S,"YBELT,'V'",000322933,

我想避免 Text::CSV,因为它没有安装在目标服务器上。意识到 CSV 比看起来更复杂,我正在使用 Perl Cookbook 中的食谱。

sub parse_csv 
  my $text = shift; #record containg CSVs
  my @columns = ();
  push(@columns ,$+) while $text =~ m
    # The first part groups the phrase inside quotes
    "([^\"\\]*(?:\\.[^\"\\]*)*)",?
      | ([^,]+),?
      | ,
    gx;
  push(@columns ,undef) if substr($text, -1,1) eq ',';
  return @columns ; # list of vars that was comma separated.

有没有人建议改进正则表达式来处理上述情况?

【问题讨论】:

第 5、6 和 7 行不是无效的 CSV 文件吗? 【参考方案1】:

请尝试使用 CPAN

没有理由不能下载Text::CSV 的副本或任何其他基于非 XS 的 CSV 解析器实现并将其安装在本地目录或项目的 lib/ 子目录中,因此它随您的项目推出一起安装。

如果您无法在项目中存储文本文件,那么我想知道您是如何编写项目的。

http://novosial.org/perl/life-with-cpan/non-root/

应该是关于如何在本地使这些进入工作状态的好指南。

不使用 CPAN 真的是灾难的根源。

请在尝试编写自己的 CSV 实现之前考虑这一点。

Text::CSV 有超过 100 行代码,包括修复的错误和边缘情况,从头开始重新编写只会让您了解 CSV 是多么糟糕。

注意:我学到了很多东西。我花了一整天的时间在 php 中获得一个可以工作的 CSV 解析器,然后我才发现一个内置的解析器已添加到更高版本中。真的是太可怕了。

【讨论】:

@Kent,谢谢...我对 Text::CSV 的主要反感是安装在另一个方面的困难.. 即:他们是否有编译器(并非所有 un*xs 都带有一个)等. 但是我重新检查了(由于你的第一篇文章)并且有一个纯 Perl 实现。 CSV_PP。谢谢。 你可能还想看看Text::xSV,它也是Pure Perl 我必须检查一下,因为 Text:CSV_PP 不适用于第五种情况,即使设置了 allow_loose_quotes 和 escape_char 也是如此。再次感谢。 我不得不与初级程序员打交道,他们要求安装阳光下的每个 CPAN 模块,而一个简单的正则表达式就足够了。 Perl 社区提倡“有不止一种方法可以做到这一点”,因此将大粗体字母喷涂成好像只有一种方法并不能帮助那些可能真正想要发现替代方法的人。 @PP。这还没有更常见的情况的一半那么糟糕,因为他们既不理解“一个简单的正则表达式”不能很好地完成工作的无数种方式,也没有理解模块的使用。有学习的野心。【参考方案2】:

您可以使用 Perl 附带的 Text::ParseWords 解析 CSV。

use Text::ParseWords;

while (<DATA>) 
    chomp;
    my @f = quotewords ',', 0, $_;
    say join ":" => @f;


__DATA__
COLLOQ_TYPE,COLLOQ_NAME,COLLOQ_CODE,XDATA
S,"BELT,FAN",003541547,
S,"BELT V,FAN",000324244,
S,SHROUD SPRING SCREW,000868265,
S,"D" REL VALVE ASSY,000771881,
S,"YBELT,"V"",000323030,
S,"YBELT,'V'",000322933,

正确解析您的 CSV....

# => COLLOQ_TYPE:COLLOQ_NAME:COLLOQ_CODE:XDATA
# => S:BELT,FAN:003541547:
# => S:BELT V,FAN:000324244:
# => S:SHROUD SPRING SCREW:000868265:
# => S:D REL VALVE ASSY:000771881:
# => S:YBELT,V:000323030:
# => S:YBELT,'V':000322933:

Text::ParseWords 的唯一问题是数据中的嵌套引号未正确转义。然而,这是糟糕的 CSV 数据,会导致大多数 CSV 解析器出现问题;-)

所以你可能会注意到

# S,"YBELT,"V"",000323030,

出来了(即引号放在“V”周围)

# S:YBELT,V:000323030:

但是如果它像这样逃跑了

# S,"YBELT,\"V\"",000323030,

然后引号将被保留

# S:YBELT,"V":000323030:

【讨论】:

仅供参考 Text::ParseWords 包含在 Perl 5 的所有版本中: perl -MModule::CoreList -l -e'print Module::CoreList->first_release_by_date("Text::ParseWords"); '打印 5.000 @draegtun 和 @mirod 感谢您的指点。我现在正在测试,谢谢。 不幸的是,除非您有状态维护解析器,否则您无法逐行解析 all CSV。一些 CSV 在带引号的字符串中有文字换行符,这曾经使解析 CSV 成为一场噩梦。即:如果您可以自己解决换行问题,您会得到:gist.github.com/1329430,但是当您按照您对文字数据的建议应用代码时;你得到这个怪物gist.github.com/1329436。像这样的细微之处就是你需要一个真正的解析器的原因;) 如果字符串连续包含 2 个引号,则会失败,例如“he said "hello"" @MikeKulls - 这是无效的 CSV,所以我并不惊讶它失败了。【参考方案3】:

这很有魅力

line 被假定为用 embeded 逗号分隔,

我的@columns = Text::ParseWords::parse_line(',', 0, $line);

【讨论】:

【参考方案4】:

经过测试;工作:-

$_.=','; # fake an ending delimiter

while($_=~/"((?:""|[^"])*)",|([^,]*),/g) 
  $cell=defined($1) ? $1:$2; $cell=~s/""/"/g; 
  print "$cell\n";


# The regexp strategy is as follows:
# First - we attempt a match on any quoted part starting the CSV line:-
#  "((?:""|[^"])*)",
# It must start with a quote, and end with a quote followed by a comma, and is allowed to contain either doublequotes - "" - or anything except a sinlge quote [^"] - this goes into $1
# If we can't match that, we accept anything up to the next comma instead, & put it into $2
# Lastly, we convert "" to " and print out the cell.

请注意,CSV 文件可以包含在引号内嵌入换行符的单元格,因此如果读取一次一行的数据,则需要这样做:

if("$pre$_"=~/,"[^,]*\z/) 
  $pre.=$_; next;

$_="$pre$_";

【讨论】:

你能把代码重新组织成一个可以以某种方式应用于正文的函数吗?我很想测试它,看看它如何与我的样本数据一起使用,就像我与其他人一样(即:gist.github.com/1329456)。 =)【参考方案5】:

使用正则表达式查找匹配对是一项非常重要且通常无法解决的任务。 Jeffrey Friedl 的Mastering regular expressions 书中有很多例子。我现在手头没有它,但我记得他也用 CSV 来做一些例子。

【讨论】:

“无法解决”?您可以使用正则表达式轻松找到匹配的引号!正则表达式不能做括号,不是因为它们匹配,而是因为它们是嵌套匹配。您不能(通常)嵌套引号。 (你可以有 \",但是这不会在旧字符串中开始一个新字符串,现在可以吗?) 谢谢 Eugene,有趣的是,我很确定 Perl Cookbook 示例取自 MRE :) 不过我会仔细检查。 /((?:[^\n,"]|"(?:[^"]|"")+")+),/g 应该更接近OP想要的,但我自己承认仍然不完美。 是的,我相信引用区域内的引用是无效的 CSV,需要一些转义机制,否则通过猜测来解决是不可能的。【参考方案6】:

您可以(尝试)使用 CPAN.pm 来简单地让您的程序安装/更新 Text::CSV。如前所述,您甚至可以将其“安装”到主目录或本地目录,然后将该目录添加到 @INC(或者,如果您不想使用 BEGIN 块,您可以使用 use lib 'dir'; - 这可能更好)。

【讨论】:

【参考方案7】:

测试:


use Test::More tests => 2;

use strict;

sub splitCommaNotQuote 
    my ( $line ) = @_;

    my @fields = ();

    while ( $line =~ m/((\")([^\"]*)\"|[^,]*)(,|$)/g ) 
        if ( $2 ) 
            push( @fields, $3 );
         else 
            push( @fields, $1 );
        
        last if ( ! $4 );
    

    return( @fields );


is_deeply(
    +[splitCommaNotQuote('S,"D" REL VALVE ASSY,000771881,')],
    +['S', '"D" REL VALVE ASSY', '000771881', ''],
    "Quote in value"
);
is_deeply(
    +[splitCommaNotQuote('S,"BELT V,FAN",000324244,')],
    +['S', 'BELT V,FAN', '000324244', ''],
    "Strip quotes from entire value"
);

【讨论】:

我知道它没有在示例数据集中列出,但是引号字符串字段中间的换行符呢?您的代码在这种情况下是否有效?您是否意识到在 CSV 中引用的字符串中允许换行?你会浪费多少时间来重新工作和重新测试你的代码来处理这种边缘情况?之前必须实现一个 CSV 解析器,我可以说出许多会破坏一个幼稚的解析器的场景,我向你保证当您可以安装并使用已经存在的东西并继续处理其他东西时,将会很快产生大量无法维护的代码。 我提供了经过测试的代码。你没有。有些人发现正则表达式很难。没关系。我非常喜欢并且喜欢正则表达式(这可能是我使用 Emacs 的原因)。 gist.github.com/1329456 # 示例数据集上的代码在字段中间包含换行符。享受 =)。

以上是关于如何使用正则表达式解析 Perl 中引用的 CSV?的主要内容,如果未能解决你的问题,请参考以下文章

在 Perl 中使用正则表达式解析属性

Perl:使用正则表达式将十六进制编码的字符串解析为数组

perl学习正则表达式

将多个正则表达式匹配之一分配给变量作为 Perl 单行(取消引用数组?)

用于解析 CSV 的正则表达式

解析posix与perl标准的正则表达式区别