【问题标题】:How to separate csv columns by awk, with a comma being the field separator?如何通过 awk 分隔 csv 列,用逗号作为字段分隔符?
【发布时间】:2019-12-22 07:10:55
【问题描述】:

我的正则表达式在 its command linefield separator 上使用 awk 的 csv 文件中不起作用。

我的 csv 用逗号分隔 (,),但有些字段本身也有逗号。

data.csv 是这样的:

t1,t2,t3,t4
field without comma,f02,f03,f04
field, with comma,f12,f13,f14
field without comma,f22,f23,f24
field without comma,f22,f23,f34

如果我们看到field, with comma,f12,f13,f14,我们有两种逗号:

  1. 逗号是数据的一部分(在字段内),例如field, with comma,并且;
  2. 逗号分隔字段,f12,f13,f14

所以我尝试了 awk,使用 -Fregex

awk -F'/\B\,/\B/' '!seen[$2]++' data.csv > resulted.csv

我的策略是:field separator 必须是无字边界 \B 中的逗号 \,

所以,我的命令没有输出resulted.csv。但是输出了一个警告:

gawk: warning: escape sequence `\B' treated as plain `B'
gawk: warning: escape sequence `\,' treated as plain `,'

而所需的result.csv 将删除重复的行,例如:

t1,t2,t3,t4
field without comma,f02,f03,f04
field, with comma,f12,f13,f14
field without comma,f22,f23,f24

【问题讨论】:

  • 这不是一个有效的 CSV 文件。如果字段包含逗号,则需要将其括在引号中。否则无法判断逗号在字段内而不是分隔符内。
  • @Barmar,我同意这不是有效的 csv(需要分隔符、分隔符、转义字段内容中的字符等)。但这就是数据。即便如此,看看我对 2 种逗号的观察。如果逗号在它的左右有边界2个单词(word= char + digit + underline)那么它是一个分隔符;如果逗号只限定一个单词,那么它就是一段内容。我的问题是正则表达式没有逃脱\B 也没有\,
  • uniq data.csv ?
  • @Cyrus,我不能在这里使用uniq,因为所有行都是唯一的。 awk 这里需要:用正则表达式分隔列(-F),获取一列用作键($2),过滤每一列(!seen),然后全部输出。
  • awk 不使用 PCRE,它不支持许多转义序列。

标签: regex csv awk ubuntu-18.04


【解决方案1】:

使用 GNU awk:

awk -F ',[^ ]' '!seen[$2]++' data.csv

输出:

t1,t2,t3,t4 不带逗号的字段,f02,f03,f04 字段,带逗号,f12,f13,f14 不带逗号的字段,f22,f23,f24

【讨论】:

  • 为什么选择 GNU awk?似乎是有效的通用 awk。
  • 用 GNU awk 测试。
  • @Cyrus,感谢您的回答,它唤醒了我的数据集。幸运的是,任何数据都有一个特殊的边界(例如特殊字符),并且只有空格才有效。再次感谢。
  • @kvantour 我在我的问题中使用 gawk(请参阅保修消息)。我不知道different implementations 之间是否存在差异(不兼容)。特别是关于正则表达式。
【解决方案2】:

如果没有 GNU awk,您可以使用 gsub", " 字符串替换为一些不冲突的字符,例如 "__""," 上正常分隔字段,然后恢复其中的逗号再次使用gsub 的字段(例如", ")。例如:

 awk -F, -v OFS=, '
    { gsub(/, /,"__"); for (i = 1; i <= NF; i++) gsub(/__/,", ", $i) }
    !seen[$0]++
' file.csv

gsub(/, /,"__") 以上将输入记录中出现的所有", " 替换为两个下划线。然后遍历每个字段,将任何"__" 替换为", ",恢复字段中的原始逗号。

使用/输出示例

根据您的数据,以上结果为:

$ awk -F, -v OFS=, '
>     { gsub(/, /,"__"); for (i = 1; i <= NF; i++) gsub(/__/,", ", $i) }
>     !seen[$0]++
> ' file.csv
t1,t2,t3,t4
field without comma,f02,f03,f04
field, with comma,f12,f13,f14
field without comma,f22,f23,f24

【讨论】:

  • 在一般情况下,我们需要使用保证不会出现在数据中的替换值(这里是"__")。考虑给定 csv 行的结果,如下所示:field__without__comma,2,3,4
  • 是的,谢谢,我虽然很清楚“用你的数据”“用一些不冲突的字符替换", "字符串”我>。这并不是要盲目地应用于所有情况的解决办法。
  • FWIW 我通常使用RS 作为“非冲突字符”,因为当 RS 是这种情况下的字符(或非正则表达式字符串(它不能出现在当前记录。
  • 有道理,你消除了你将用来包围非贪婪选择的内容,(例如'{''}'),然后你设置搜索结束范围的开始和结束加上"{ abc } def }" 允许括号{ ... } 之间的match 导致非贪婪匹配。很高兴我在那里看到了多层次的思维过程,如果没有更多的经验,这不会很明显。
  • 欢迎您,但深度思考的功劳归于@EdMorton。我敢打赌,我通过 cmets 学到的东西和我希望帮助其他人的答案一样多。
【解决方案3】:

如果您的意图是使用 t2 列作为键值,那么您应该这样做:

$ awk -F, '!seen[$(NF-2)]++' data.csv
t1,t2,t3,t4
field without comma,f02,f03,f04
field, with comma,f12,f13,f14
field without comma,f22,f23,f24

如果要使用 t1 列作为键,那么您应该这样做:

$ awk '{key=$0; sub(/(,[^,]+){3}$/,"",key)} !seen[key]++' data.csv
t1,t2,t3,t4
field without comma,f02,f03,f04
field, with comma,f12,f13,f14

如果是其他问题,请澄清您的问题并更新示例。

【讨论】:

  • 我选择了this answer,因为它在我的示例和真实数据集中得到了解决。而且更简单。但是我喜欢你在使用反向 NF 时的方法。它也奏效了。感谢您的贡献。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-02-06
  • 1970-01-01
  • 2010-11-29
  • 2023-02-01
  • 1970-01-01
  • 2017-06-14
  • 1970-01-01
相关资源
最近更新 更多