30

我正在使用 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 脚本。我的问题是:awk 能解决这个问题吗?如果是,我该怎么做?

谢谢你。

4

12 回答 12

30

One way using GNU awk and FPAT

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

Result:

192
于 2012-10-18T14:26:19.107 回答
7

我在用

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

用 gawk 定义字段。我发现当该字段为空时,这无法识别正确数量的字段。因为“+”要求字段中至少有 1 个字符。我将其更改为:

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

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

我还发现 GNU Awk User Guide 也有这个问题。 https://www.gnu.org/software/gawk/manual/html_node/Splitting-By-Content.html

于 2015-08-08T04:19:43.117 回答
5

您可能最好在 perl 中使用 Text::CSV 来执行此操作,因为这是一个快速且强大的解决方案。

于 2010-06-30T17:32:27.140 回答
4

对于像这样简单的输入文件,您只需编写一个小函数即可将引号之外的所有真实 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>

只有当您必须处理引号内的嵌入换行符和嵌入的转义引号时,它才会变得复杂,即使这样也不会太难,而且之前都已经完成了......

请参阅使用 awk 有效解析 CSV 的最可靠方法是什么?了解更多信息。

于 2012-10-18T14:20:06.880 回答
3

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

这是命令:

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

代码见https://github.com/dbro/csvquote

于 2013-05-04T21:08:15.387 回答
2

FPAT 是一个优雅的解决方案,因为它可以处理可怕的逗号内引号问题,但是要对最后一列中的一列数字求和,而不管前面的分隔符有多少,$NF 效果很好:

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

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

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

于 2014-04-06T01:59:14.057 回答
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 没有修改源数据的权限,因此这种方法不适合他。

于 2010-06-29T07:19:20.557 回答
2

这篇文章确实帮助我解决了同样的数据字段问题。大多数 CSV 会在包含空格或逗号的字段周围加上引号。这会弄乱 awk 的字段计数,除非您将它们过滤掉。

如果您需要那些包含垃圾的字段中的数据,那么这不适合您。ghostdog74提供了答案,它清空了该字段,但最终保持了总字段数,这是保持数据输出一致的关键。我不喜欢这个解决方案如何引入新行。这是我使用的这个解决方案的版本。前三个字段在数据中从来没有出现过这个问题。包含客户姓名的第四个字段经常这样做,但我需要该数据。显示问题的其余字段我可以毫无问题地丢弃,因为我的报告输出中不需要它。所以我首先非常明确地清除了第四个字段的垃圾并删除了前两个引号实例。然后我申请什么ghostdog74给清空其中包含逗号的剩余字段 - 这也删除了引号,但我printf用来将数据保存在单个记录中。从我的 8000 多行杂乱数据中,我从 85 个字段开始,在所有情况下都以 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 提供的出色解决方案!

网人256/

于 2012-10-18T00:35:20.837 回答
1

如果您确定“值”列始终是最后一列:

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

NF代表字段数,所以$NF是最后一列

于 2010-06-30T17:44:43.483 回答
1

Perl 等成熟的 CSV 解析器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

于 2015-11-03T00:37:33.567 回答
0

您可以尝试通过 perl 正则表达式将文件管道化,以将引用的 , 转换为像 | 之类的其他内容。

cat test.csv | perl -p -e "s/(\".+?)(,)(.+?\")/\1\|\3/g" | awk -F, '{...

上面的正则表达式假定双引号内总是有一个逗号。所以需要做更多的工作才能使逗号成为可选

于 2021-09-09T20:27:28.863 回答
-6

您在 awk 中编写一个函数,如下所示:

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

你可以在你的脚本中加入这个函数并检查第三个字段是否是数字。如果不是数字,那么去第四个字段,如果第四个字段反过来不是数字,去第五个......直到你达到一个数值。可能一个循环在这里会有所帮助,并将其添加到总和中。

于 2010-06-30T17:14:21.590 回答