【问题标题】:How to get substring of the line enclosed in double quotes如何获取用双引号括起来的行的子字符串
【发布时间】:2015-11-21 15:40:17
【问题描述】:

我有一个输入字符串:

ACC000121,2290,"01009900,01009901,01009902,01009903,01009904",4,5,6

如果我使用拆分功能,我会得到奇怪的输出。

my ($field1, $field2, $field3, $field4) = "";
while (<DATAFILE>) {
    $row = $_;
    $row =~ s/\r?\n$//;
    ($field1, $field2, $field3, $field4) = split(/,/, $row);
}

我得到的输出是:

field1 :: ACC000121
field2 :: 2290
field3 :: "01009900
field4 :: 01009901

预期输出:

field1 = ACC000121
field2 = 2290
field3 = 01009900,01009901,01009902,01009903,01009904
field4 = 4
field5 = 5
field6 = 6

我在 Perl 方面很弱。请帮帮我

【问题讨论】:

  • 看起来split 正在按设计工作。此外,您的第一行并没有做您认为它正在做的事情。你有一个空字符串和 3 个undefs。
  • 是否可以将整个双引号字符串子串成一个标量变量。如何实现
  • 我很好奇,当您的代码显式只处理 4 个字段时,您期望输出中有 6 个字段。

标签: perl


【解决方案1】:

如果你有 CSV 数据,你真的想使用Text::CSV 来解析它。正如您所发现的,解析 CSV 数据通常并不像用逗号分割那么简单,Text::CSV 可以为您处理所有边缘情况。

use strict;
use warnings;

use Data::Dump;
use Text::CSV;

my $csv = Text::CSV->new;

while (<DATA>) {
    $csv->parse($_);
    my @fields = $csv->fields;
    dd(\@fields);
}

__DATA__
ACC000121,2290,"01009900,01009901,01009902,01009903,01009904",4,5,6

输出:

[
  "ACC000121",
  2290,
  "01009900,01009901,01009902,01009903,01009904",
  4,
  5,
  6,
]

【讨论】:

  • 我建议使用 Text::CSVs getline 方法,因为它还可以处理多行 csv 文件(有些 csv 文件的字段中包含换行符)。语法几乎相同:while (my $row = $csv->getline($FH)) {}
【解决方案2】:

我同意 Matt Jacobanswer — 你应该用 Text::CSV 解析 CSV,除非你有充分的理由不这样做。

如果您打算使用正则表达式来处理它,我认为使用m// 会比使用split 做得更好。例如,这似乎涵盖了大多数单行 CSV 数据变体,尽管它不会像 Text::CSV 那样删除引用字段周围的引号 — 这需要单独的后处理步骤。

use strict;
use warnings;

sub splitter
{
    my($row) = @_;
    my @fields;
    my $i = 0;

    while ($row =~ m/((?=,)|[^",][^,]*|"([^"]|"")*")(?:,|$)/g)
    {
        print "Found [$1]\n";
        $fields[$i++] = $1;
    }

    for (my $j = 0; $j < @fields; $j++)
    {
        print "$j = [$fields[$j]]\n";
    }
}

my $row;

$row = q'ACC000121,2290,"01009900,01009901,01009902,01009903,01009904",4,5,6';
print "Row 1: $row\n";
splitter($row);

$row = q'ACC000121,",",2290,"01009900,""aux data"",01009902,01009903,01009904",,5"abc",6,""';
print "Row 2: $row\n";
splitter($row);

显然,其中包含相当多的诊断代码。输出(来自 Mac OS X 10.11.1 上的 Perl 5.22.0)是:

Row 1: ACC000121,2290,"01009900,01009901,01009902,01009903,01009904",4,5,6
Found [ACC000121]
Found [2290]
Found ["01009900,01009901,01009902,01009903,01009904"]
Found [4]
Found [5]
Found [6]
0 = [ACC000121]
1 = [2290]
2 = ["01009900,01009901,01009902,01009903,01009904"]
3 = [4]
4 = [5]
5 = [6]
Row 2: ACC000121,",",2290,"01009900,""aux data"",01009902,01009903,01009904",,5"abc",6,""
Found [ACC000121]
Found [","]
Found [2290]
Found ["01009900,""aux data"",01009902,01009903,01009904"]
Found []
Found [5"abc"]
Found [6]
Found [""]
0 = [ACC000121]
1 = [","]
2 = [2290]
3 = ["01009900,""aux data"",01009902,01009903,01009904"]
4 = []
5 = [5"abc"]
6 = [6]
7 = [""]

在 Perl 代码中,匹配是:

m/((?=,)|[^",][^,]*|"([^"]|"")*")(?:,|$)/

这会查找并捕获(在$1 中)后跟逗号的空字段,或后跟零个或多个非逗号的双引号以外的其他内容,或后跟一系列零次或多次出现“不是双引号或两个连续的双引号”和另一个双引号;然后它需要一个逗号或字符串结尾。

处理多行字段需要做更多的工作。删除转义的双引号也需要更多的工作。

使用Text::CSV 更简单,更不容易出错(它可以处理比这更多的变体)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-11
    • 1970-01-01
    • 2014-09-26
    相关资源
    最近更新 更多