我正在使用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脚本。 我的问题是:能解决这个问题吗?如果是,我该怎么做?
谢谢。
答案 0 :(得分:19)
使用GNU awk
和FPAT
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}'
答案 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_whitespace
。 Text::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数值。可能一个循环在这里会有所帮助,并将它添加到总和中。