【问题标题】:How can I read a CSV file if only non-empty fields are wrapped by double quotes?如果只有非空字段用双引号括起来,我如何读取 CSV 文件?
【发布时间】:2016-06-01 04:15:24
【问题描述】:

我正在尝试在 Bash 脚本中读取 CSV 文件。我使用gawk 并指定FPAT 成功实现了这一点,例如:

gawk -v LOGFILE="${LOGFILE}" 'BEGIN {
    FPAT = "([^,]+)|(\"[^\"]+\")"
    }
NR == 1{
    # doing some logic with header
}
NR >= 2{
    # doing some logic with fields
}' <filename>

这里的问题是,文件包含如下数据:

"RAM","31st street, Bengaluru, India",,,,"7865431234",,"VALID"

现在,有了这些数据,我得到了错误的数据,因为它忽略了逗号,这给了我提取数据的错误位置编号。 例如,它告诉“7865431234”出现在第 3 位,而它在第 6 位。

任何人都可以建议更改以获得正确的字段位置吗?

【问题讨论】:

  • 值本身可以包含逗号吗?
  • 是的,因为第二个字段是地址。它可以包含任意数量的逗号。
  • 我会将 perl 与 Text::CSV 模块一起使用。
  • 我对 perl 不太熟悉。有没有办法使用 bash 脚本来实现?
  • @java-bash-spring:无论如何,你使用的是gawk,而不是bash。

标签: bash csv unix awk


【解决方案1】:

您的FPAT 要求每个字段至少包含一个字符,但您希望识别具有零个字符的空字段。添加允许零字符的FPAT 的替代方案:

gawk 'BEGIN { FPAT = "([^,]+)|(\"[^\"]+\")|" }
{ printf "%d:%d:", NR, NF; for (i = 1; i <= NF; i++) printf("[%s]", $i); print "" }'

注意FPAT 末尾的额外|。该操作仅标识记录号、字段数,并用方括号将每个字段的值括起来。

当您的数据字符串提供给该脚本时,输出为:

1:8:["RAM"]["31st street, Bengaluru, India"][][][]["7865431234"][]["VALID"]

这四个空字段显示得很清楚。

现在你要做的就是处理:

"Mr ""Manipulator"", the Artisan","29th Street, Delhi, India",,,"",,,"INVALID"

引用值中有双引号。这并不难管理:

gawk 'BEGIN { FPAT = "([^,]+)|(\"([^\"]|\"\")*\")[^,]*|" }
{ printf "%d:%d:", NR, NF; for (i = 1; i <= NF; i++) printf("%d[%s]", i, $i); print "" }' "$@"

FPAT 表示一个字段是:

  • 一系列非逗号,

  • 或者它是以双引号开头的字段,包含零个或多个实例:

    • 非引号,或
    • 两个双引号

    后跟双引号和可选的非逗号数据

  • 或者它是空的

请注意,“可选非逗号数据”应为空,并且仅出现在格式错误的 CSV 数据中。

给定输入数据:

"RAM","31st street, Bengaluru, India",,,,"7865431234",,"VALID"
"Mr ""Manipulator"", the Artisan","29th Street, Delhi, India",,,,,,"INVALID"
"Some","","Empty","",Fields "" Wrapped,"",in quotes
"Malformed" CSV,Data,"Note it has data after" a close quote,"and before a comma,",,"INVALID"

这会产生:

1:8:1["RAM"]2["31st street, Bengaluru, India"]3[]4[]5[]6["7865431234"]7[]8["VALID"]
2:8:1["Mr ""Manipulator"", the Artisan"]2["29th Street, Delhi, India"]3[]4[]5[]6[]7[]8["INVALID"]
3:7:1["Some"]2[""]3["Empty"]4[""]5[Fields "" Wrapped]6[""]7[in quotes]
4:6:1["Malformed" CSV]2[Data]3["Note it has data after" a close quote]4["and before a comma,"]5[]6["INVALID"]

请注意,字段编号作为括号数据的前缀包含在内(因此我稍微调整了打印格式)。

关于此不处理的唯一格式是可以在字段数据中嵌入换行符的格式——根据基于行的输入的性质,它假定没有字段被拆分为多行。 (这也意味着它不会正确识别以双引号开头并且在行尾之前没有匹配的双引号的字段。我想您可以添加一个替代方法来识别它。最好只是使数据正确。)


请注意Sobrique 的answer 中的建议,以使用旨在处理 CSV 的工具来处理 CSV。这通常是一个好主意,你必须处理的变化集越复杂,它的想法就越好。这接近于您应该考虑使用的复杂正则表达式。另请注意,尽管RFC 4180 正式而严格地定义了一个 CSV 版本,但有多个程序(包括 MS Office)可以处理不同但相关的格式。

【讨论】:

    【解决方案2】:

    如果您有需要解析的 csv,那么虽然您通常可以使用正则表达式破解它,但使用解析器要容易得多。

    类似这样的:

    #!/usr/bin/env perl
    
    use strict;
    use warnings;
    use Text::CSV;
    
    my $csv = Text::CSV -> new; 
    open ( my $input, '<', 'flarg.csv' ) or die $!; 
    
    while ( my $row = $csv -> getline ( $input ) ) {
       if ( $. == 1 ) {
            # do first row stuff; 
            print "Header: ", join ",", @$row,"\n";
       }
       else {
           print join "\n", @$row;
       }
    }
    

    或者更简单——使用核心Text::ParseWords。

    #!/usr/bin/env perl
    
    use strict;
    use warnings;
    use Text::ParseWords;
    
    while ( my $line = <DATA> ) {
        my @fields = parse_line(',', 1, $line);
        print join "\n", @fields;
    } 
    __DATA__
    "RAM","31st street, Bengaluru, India",,,,"7865431234",,"VALID"
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-09-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-14
      • 2014-02-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多