awk可以处理在引用字段中包含逗号的CSV文件吗?

时间:2010-06-29 06:35:26

标签: csv awk field text-parsing quoting

我正在使用awk来计算csv文件中一列的总和。数据格式如下:

id, name, value
1, foo, 17
2, bar, 76
3, "I am the, question", 99

我正在使用这个awk脚本计算总和:

awk -F, '{sum+=$3} END {print sum}'

name字段中的某些值包含逗号,这会破坏我的awk脚本。 我的问题是:能解决这个问题吗?如果是,我该怎么做?

谢谢。

11 个答案:

答案 0 :(得分:19)

使用GNU awkFPAT

的一种方法
awk 'BEGIN { FPAT = "([^, ]+)|(\"[^\"]+\")" } { sum+=$3 } END { print sum }' file.txt

结果:

192

答案 1 :(得分:4)

使用Text :: CSV在perl中进行此操作可能会更好,因为这是一种快速而强大的解决方案。

答案 2 :(得分:3)

对于简单的输入文件,您只需编写一个小函数将引号之外的所有实际FS转换为其他值(我选择RS,因为记录分隔符不能是记录的一部分)然后将其用作FS,例如:

$ cat decsv.awk
BEGIN{ fs=FS; FS=RS }

{
   decsv()

   for (i=1;i<=NF;i++) {
       printf "Record %d, Field %d is <%s>\n" ,NR,i,$i
   }
   print ""
}

function decsv(         curr,head,tail)
{
   tail = $0
   while ( match(tail,/"[^"]+"/) ) {
       head = substr(tail, 1, RSTART-1);
       gsub(fs,RS,head)
       curr = curr head substr(tail, RSTART, RLENGTH)
       tail = substr(tail, RSTART + RLENGTH)
   }
   gsub(fs,RS,tail)
   $0 = curr tail
}

$ cat file
id, name, value
1, foo, 17
2, bar, 76
3, "I am the, question", 99

$ awk -F", " -f decsv.awk file
Record 1, Field 1 is <id>
Record 1, Field 2 is <name>
Record 1, Field 3 is <value>

Record 2, Field 1 is <1>
Record 2, Field 2 is <foo>
Record 2, Field 3 is <17>

Record 3, Field 1 is <2>
Record 3, Field 2 is <bar>
Record 3, Field 3 is <76>

Record 4, Field 1 is <3>
Record 4, Field 2 is <"I am the, question">
Record 4, Field 3 is <99>

当你必须在引号中处理嵌入的换行符和嵌入的转义引号时,它变得很复杂,即使这样也不是太难,而且之前都已经完成了......

有关详细信息,请参阅What's the most robust way to efficiently parse CSV using awk?

答案 3 :(得分:3)

您可以使用我编写的名为csvquote的小脚本来帮助awk处理包含逗号(或换行符)的数据字段。它用非打印字符替换带引号字段内的违规逗号。如果需要,您可以稍后恢复这些逗号 - 但在这种情况下,您不需要。

这是命令:

csvquote inputfile.csv | awk -F, '{sum+=$3} END {print sum}'

请参阅https://github.com/dbro/csvquote了解代码

答案 4 :(得分:3)

我正在使用

`FPAT="([^,]+)|(\"[^\"]+\")" `

用gawk定义字段。我发现当字段为空时,这不能识别正确的字段数。因为“+”在字段中至少需要1个字符。 我改成了:

`FPAT="([^,]*)|(\"[^\"]*\")"`

并将"+"替换为"*"。它工作正常。

我还发现GNU Awk用户指南也存在这个问题。 https://www.gnu.org/software/gawk/manual/html_node/Splitting-By-Content.html

答案 5 :(得分:2)

您始终可以从源头解决问题。在名称字段周围加上引号,就像“我是,问题”字段一样。这比为此花费时间编写变通办法要容易得多。

更新(正如丹尼斯要求的那样)。一个简单的例子

$ s='id, "name1,name2", value 1, foo, 17 2, bar, 76 3, "I am the, question", 99'

$ echo $s|awk -F'"' '{ for(i=1;i<=NF;i+=2) print $i}'
id,
, value 1, foo, 17 2, bar, 76 3,
, 99

$ echo $s|awk -F'"' '{ for(i=2;i<=NF;i+=2) print $i}'
name1,name2
I am the, question

如您所见,通过将分隔符设置为双引号,属于“引号”的字段始终为偶数。由于OP无法修改源数据,因此这种方法不适合他。

答案 6 :(得分:2)

如果您确定'value'列始终是最后一列:

awk -F, '{sum+=$NF} END {print sum}'

NF表示字段数,因此$ NF是最后一列

答案 7 :(得分:2)

本文确实帮助我解决了同样的数据字段问题。大多数CSV会在带有空格或逗号的字段周围添加引号。除非你把它们过滤掉,否则会混淆awk的字段数。

如果您需要包含垃圾的那些字段中的数据,则不适合您。 ghostdog74提供了答案,清空该字段但最后保持总字段数,这是保持数据输出一致的关键。我不喜欢这个解决方案如何引入新线路。这是我使用的这个解决方案的版本。前三个字段在数据中从未出现过这个问题。包含客户名称的第四个字段经常这样做,但我需要这些数据。显示问题的其余字段我可以毫无问题地丢弃,因为我的报告输出中不需要它。所以我首先非常具体地清除了第4个字段的垃圾,并删除了前两个引号实例。然后我应用ghostdog74给出的内容清空其中包含逗号的其余字段 - 这也删除了引号,但我使用printf将数据保存在单个记录中。我从85个字段开始,在所有情况下从我的8000多行杂乱数据中得到85个字段。一个完美的分数!

grep -i $1 $dbfile | sed 's/\, Inc.//;s/, LLC.//;s/, LLC//;s/, Ltd.//;s/\"//;s/\"//' | awk -F'"' '{ for(i=1;i<=NF;i+=2) printf ($i);printf ("\n")}' > $tmpfile

使用逗号清空字段但仍保留记录的解决方案当然是:

awk -F'"' '{ for(i=1;i<=NF;i+=2) printf ($i);printf ("\n")}

感谢ghostdog74的伟大解决方案!

NetsGuy256 /

答案 8 :(得分:1)

FPAT是优雅的解决方案,因为它可以在引号问题中处理可怕的逗号,但是无论前面的分隔符数量如何,要总结最后一列中的一列数字,$ NF效果很好:

awk -F"," '{sum+=$NF} END {print sum}'

要访问倒数第二列,您可以使用:

awk -F"," '{sum+=$(NF-1)} END {print sum}'

答案 9 :(得分:1)

完全成熟的CSV解析器(如Perl的Text::CSV_XS)专门用于处理这种奇怪现象。

perl -MText::CSV_XS -lne 'BEGIN{$csv=Text::CSV_XS->new({allow_whitespace => 1})} if($csv->parse($_)){@f=$csv->fields();$sum+=$f[2]} END{print $sum}' file

由于输入数据在逗号分隔符周围有空格,因此需要

allow_whitespaceText::CSV_XS的旧版本可能不支持此选项。

我在答案中提供了Text::CSV_XS的更多解释:parse csv file using gawk

答案 10 :(得分:-4)

你在awk中编写一个函数如下:

$ awk 'func isnum(x){return(x==x+0)}BEGIN{print isnum("hello"),isnum("-42")}'
0 1

你可以在你的脚本中加入这个函数并检查第三个字段是否为数字。如果不是数字则转到第4个字段,如果第4个字段不是数字则转到第5个字段...直到你到达a数值。可能一个循环在这里会有所帮助,并将它添加到总和中。