在awk中,双引号内的分隔符需要进行转义处理。

39
我使用awk解析逗号分隔的数据,因为输入是csv文件。然而,数据中存在用双引号 ("...") 转义的逗号。
filed1,filed2,field3,"field4,FOO,BAR",field5

我该如何在双引号内忽略逗号“,”,以便可以使用awk正确解析输出?我知道我们可以在Excel中做到这一点,但在awk中应该如何操作?


对于非常全面的描述,请查看此处:>http://backreference.org/2010/04/17/csv-parsing-with-awk/(存档版本)。但这不是解析,而是使用正则表达式作弊。对于比页面上的示例更复杂的任何内容,您应该使用Perl/Python,并使用像Python中的“csv”之类的解析器库。 - Chris
使用Perl进行Text:CSV解析的精彩文章:http://perlmeme.org/tutorials/parsing_csv.html - joomanji
1
请参考 https://dev59.com/XlcO5IYBdhLWcg3wrTcs 中有关使用 awk 解析 CSV 的内容。 - Ed Morton
3个回答

30
使用GNU awk 4非常简单:
zsh-4.3.12[t]% awk '{ 
 for (i = 0; ++i <= NF;)
   printf "field %d => %s\n", i, $i
 }' FPAT='([^,]+)|("[^"]+")' infile
field 1 => filed1
field 2 => filed2
field 3 => field3
field 4 => "field4,FOO,BAR"
field 5 => field5

根据OP的要求添加一些注释。

GNU awk手册中的“按内容定义字段”

FPAT的值应该是一个提供正则表达式的字符串。这个正则表达式描述了每个字段的内容。在上面提供的CSV数据的情况下,每个字段都是“不是逗号的任何东西”或“双引号,不是双引号的任何东西和一个结束双引号”。如果写成正则表达式常量,我们会有/([^,]+)|("[^"]+")/。将其写为字符串需要我们转义双引号,导致:

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

使用两次+,这对于空字段不起作用,但也可以进行修复:

如所述,用于FPAT的正则表达式要求每个字段至少包含一个字符。简单的修改(将第一个“+”更改为“*”)允许字段为空:

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


1
这太棒了@DimitreRadoulov。你对gawk有非常透彻的了解:-)。我已经在这里使用了你的建议,并引用了这个答案。希望没问题。+1 - jaypal singh
1
跳转到 这里 的解决方法对我的数据似乎不起作用。事实上,它基于空格(FS 的默认值是一个空格)拆分字段,而不是 FPAT 指定的正则表达式。添加 FS="," 似乎让 awk 完全忽略了 FPAT,因为它没有转义带有嵌入逗号的引号字段。 - chrisbunney
嗨@chrisbunney,你能确认一下你是否在使用GNU awk 4吗?为什么要设置FS? - Dimitre Radoulov
2
内置变量FPAT是在GNU awk 4中引入的。我刚刚在您原始帖子中发布了一个Perl解决方案。 - Dimitre Radoulov
Mac用户可以执行brew install gawk,并使用gawk代替awk。 - scottgwald
显示剩余2条评论

12

当引号内有换行符和逗号时,FPAT 可以工作,但引号内有双引号时就无法工作,例如:

field1,"field,2","but this field has ""escaped"" quotes"

您可以使用我编写的一个简单的包装程序csvquote,使数据易于awk解释,然后恢复有问题的特殊字符,如下所示:

csvquote inputfile.csv | awk -F, '{print $4}' | csvquote -u

请查看 https://github.com/dbro/csvquote 以获取代码和文档。


3
嗨@DBro, 通过稍微扩展FPAT正则表达式,可以解决双引号被计入的问题:BEGIN { FPAT = "(\"([^\"]|\"\")*\")|([^,\"]*)" } - colemar

0

完整的CSV解析器,如Perl的Text::CSV_XS,专门用于处理这种奇怪的情况。

假设您只想打印第4个字段:

perl -MText::CSV_XS -lne 'BEGIN{$csv=Text::CSV_XS->new()} if($csv->parse($_)){ @f=$csv->fields(); print "\"$f[3]\"" }' file

输入行被拆分成数组@f
由于Perl从0开始索引,因此第4个字段是$f[3]

我在这里提供了有关Text::CSV_XS的更多解释:使用gawk解析csv文件


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接