使用(g)awk替换包含字符串的分隔符的csv文件中的列值

3

我是使用gawk 4.0.1的,我知道如何替换CSV文件中的列值,例如:

> ROW='1,2,3,4,5,6'
> echo $ROW | gawk -F, -vOFS=, '$2="X"'
1,X,3,4,5,6

然而,我正在处理一个包含定界符的字符串文件。读取列没有问题,但是在替换值时会插入额外的分隔符:

> ROW='1,"2,3",4,5,6'
> echo $ROW | gawk -vOFS=, -vFPAT='[^,]*|"[^"]*"' '{print $2}'
"2,3"
> echo $ROW | gawk -vOFS=, -vFPAT='[^,]*|"[^"]*"' '$2="X"'
1,X,,4,5,6

这是我期望的内容:
> echo $ROW | gawk -vOFS=, -vFPAT='[^,]*|"[^"]*"' '$2="X"'
1,X,4,5,6

值为“2,3”的内容被替换成了“X,”。我该如何解决?

编辑:我没有提到我也有为空的字段。因此,更好的行示例应该是:

ROW='1,,"2,3",4,5,6'

编辑 2: 根据Dawg的答案,我得出结论,在纯awk中不可能实现。虽然我同意使用Python的解决方案更好,但唯一使用awk的解决方案是包含一些预处理和后处理来处理空字段。

#/bin/bash
ROW='1,,"2,3",4,"",5'
for col in {1..6}; do 
    echo $ROW |\ 
        sed 's:,,:, ,:' |\ 
        gawk -v c=$col -v OFS=, -v FPAT='([^,]+)|("[^\"]*")' '$c="X"' |\
        sed 's:, ,:,,:g'
done

输出:

X,,"2,3",4,"",5
1,X,"2,3",4,"",5
1,,X,4,"",5
1,,"2,3",X,"",5
1,,"2,3",4,X,5
1,,"2,3",4,"",X

非常奇怪,因为 echo $ROW | gawk -vOFS=, -vFPAT='[^,]*|"[^"]*"' '{print $1,"X",$3}' 可以执行。我还尝试过 $0=$0 重新解释字符串,但没有成功。 - fedorqui
1
@gospes 为什么你依赖于 awk?你想从其他工具中得到答案吗? - Avinash Raj
1
尝试使用+和括号([^,]+)|("[^"]+") - user3442743
3
根据精细手册,您应该在备选运算符“|”的两侧使用分组。也就是说(始终来自之前的源代码),FPAT = "([^,]+)|(\"[^\"]+\")" --- hth - gboffi
@Jidder:那也不行,或者至少,如果我有空字段。 - gospes
3个回答

2
$ echo $ROW | awk -vOFS=, -vFPAT="([^,]+)|(\"[^\"]+\")" '$2="X"'
1,X,4,5,6

我使用了GNU Awk手册中的模式4.7 按内容定义字段
与相同模式中的*进行比较:
$ echo $ROW | awk -vOFS=, -vFPAT="([^,]*)|(\"[^\"]*\")" '$2="X"'
1,X,,4,5,6

答案是 -(对于这个有限的例子)- 使用-vFPAT="([^,]+)|(\"[^\"]+\")",但是这不能处理像1,"2,3",4,,"","should be 6th field"这样的空字段。
下面是包含两种类型空字段(,,"")的结果:
$ echo $ROW2 | awk -vOFS=, -vFPAT="([^,]+)|(\"[^\"]+\")" '$2="X"'
1,X,4,"","should be 6th field"
      ^^                    - missing the ',,' field
            ^^^             - now the 5th field  -- BUG!

根据惯例,应该将ROW2视为具有6个字段的数据,空字段,,""都计算为一个字段。如果不将空字段视为字段,则会在空字段后失去对各个字段的计数。CSV在awk正则表达式中的使用也增加了很多复杂性。
要处理CSV,需要知道它是出人意料的复杂,仅使用awk或正则表达式并不容易 另一种处理CSV的解决方案是使用Perl或Python,并使用更为复杂和标准化的CSV库来处理。在Python的情况下,它是Python标准发行版的一部分。
以下是一个Python解决方案,它完全兼容RFC 4180
$ echo $ROW | python -c '
> import csv, fileinput
> for line in csv.reader(fileinput.input()):
> print ",".join(e if i!=1 else "X" for i, e in enumerate(line))'
1,X,4,5,6

这句话的意思是:“它可以轻松处理更复杂的 CSV。”下面这段文字是一个例子,其中包含了 4 行、每行 5 列的 CSV 文件,在引号字段中有 CRLF,引号字段中有转义引号,并且还有两种类型的空白字段(",," 和 "")。
1,"2,3",4,5,6
"11,12",13,14,15,16
21,"22,
23",24,25,"26
27"
31,,"33\"not 32\"","",35

使用相同的脚本(在正常情况下,您可能会使用 str ),使用 repr 查看完整字段值,所有这些情况都根据RFC 4180正确处理:
$ cat /tmp/3.csv | python -c '
import csv, fileinput
for line in csv.reader(fileinput.input()):
   print ",".join(repr(e) if i!=1 else "X" for i, e in enumerate(line))'
'1',X,'4','5','6'
'11,12',X,'14','15','16'
'21',X,'24','25','26\n27'
'31',X,'33\\not 32\\""','','35'

这在使用 awk 中很困难,因为 \n 定义了每个记录,我们无法正确处理空字段并且不能正确处理转义引号:
$ cat /tmp/3.csv | awk -vOFS=, -vFPAT='[^,]+|"[^"]*"' '$2="X"'
1,X,4,5,6
"11,12",X,14,15,16
21,X
23",X,25,"26
27",X
31,X,"",35

现在您需要重新定义RS为一个正则表达式,找到CR周围的引号,并使用awk读取多行... 添加对转义引号的支持... 使用更复杂的正则表达式来拆分字段... 很复杂... 祝好运!

0
  1. 使用以下命令可以将第二个字段替换为X:$ echo $ROW | gawk -vOFS=, -vFPAT='[^,]+|"[^"]\emph{.}"' '$2="X"'

应该在 [^"] 之后。

  1. 使用以下命令也可以将第二个字段替换为X:echo $ROW | gawk -vOFS=, -vFPAT='[^,]+|"[^"].*"' '$2="X"'

对于 ROW='1,"2,3",4,5,6',这两个答案都会产生输出 1,x,4,5,6。


0

输出是为了

$ ROW='1,"2,3",4,5,6' 
$ echo $ROW | gawk -vOFS=, -vFPAT='[^,]+|"[^"].*"' '$2="X"'
1,X,4,5,6

这两个命令都可以正常工作。在第二个命令中,复制到这里时错过了*

Perl:

$var='1,"2,3",4,5,6';
$var=~s/\".*\"/X/g;
print $var;

1
请您能否详细说明并提供一个例子呢?您是说您添加了一个逗号,就像“X,”这样吗? - kkuilla
是的。在将整个双引号放入$2时,使用X添加逗号。 - karthick Sundaram
1
你能否更新你的回答并展示它确实可以使用提问者提供的数据吗?否则,它只是一条评论,不是真正的答案,可能会被删除。 - kkuilla
1
@karthickSundaram 通过添加一个问号,如何消除这个多余的逗号? - Tensibai
echo $ROW | gawk -vOFS=, -vFPAT='[^,]+|"[^"].*"' '$2="X"' *这里粘贴时漏掉了。 - karthick Sundaram
$ echo $ROW | gawk -vOFS=, -vFPAT='[^,]+|"[^"]."' '$2="X"' 这里的 . 在 [^"] 后面也可以正常工作。 - karthick Sundaram

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