Bash 如何使awk忽略双引号内的字段分隔符?

Bash 如何使awk忽略双引号内的字段分隔符?,bash,shell,awk,Bash,Shell,Awk,我需要删除逗号分隔值文件中的两列。 考虑CSV文件中的以下行: "abc@xyz.com,www.example.com",field2,field3,field4 "def@xyz.com",field2,field3,field4 现在,我想在最后得到的结果是: "abc@xyz.com,www.example.com",field4 "def@xyz.com",field4 我使用了以下命令: awk 'BEGIN{FS=OFS=","}{print $1,$4}' 但是引号中的嵌入

我需要删除逗号分隔值文件中的两列。 考虑CSV文件中的以下行:

"abc@xyz.com,www.example.com",field2,field3,field4
"def@xyz.com",field2,field3,field4
现在,我想在最后得到的结果是:

"abc@xyz.com,www.example.com",field4
"def@xyz.com",field4
我使用了以下命令:

awk 'BEGIN{FS=OFS=","}{print $1,$4}'
但是引号中的嵌入逗号产生了一个问题,下面是我得到的结果:

"abc@xyz.com,field3
"def@xyz.com",field4

现在我的问题是如何让awk忽略双引号中的“?”

在示例输入文件中,它是第一个字段,并且只有第一个字段被引用。如果这是真的,那么将下面的方法作为删除第二和第三列的方法:

$ awk -F, '{for (i=1;i<=NF;i++){printf "%s%s",(i>1)?",":"",$i; if ($i ~ /"$/)i=i+2};print""}' file
"abc@xyz.com,www.example.com",field4
"def@xyz.com",field4
$awk-F,'{for(i=1;i1)?,“:”,$i;if($i~/“$/)i=i+2};打印“}”文件
"abc@xyz.com,www.example.com”,第4栏
"def@xyz.com“,字段4
正如在评论中提到的,awk不理解引号分隔符。这个解决方案通过查找以引号结尾的第一个字段来解决这个问题。然后它跳过后面的两个字段

细节
  • for(i=1;i1)“,”:“,$i

    这将打印字段
    i
    。如果它不是第一个字段,则该字段前面会有一个逗号

  • if($i~/“$/)i=i+2

    如果当前字段以双引号结尾,则会将字段计数器增加2。这就是我们跳过字段2和字段3的方式

  • print”“

    完成
    for
    循环后,将打印一条换行符


这不是bash/awk解决方案,但我建议您通过
pip install csvkit
安装它。它提供了一组专门用于CSV的命令行工具,包括,它完全满足您的要求:

csvcut --columns=1,4 <<EOF
"abc@xyz.com,www.example.com",field2,field3,field4
"def@xyz.com",field2,field3,field4
EOF
它去掉了不必要的引语,我想这不应该是个问题


阅读CSVKit的文档。ThoughtBot介绍了这个工具,这是我了解CSVKit的地方。

无论引用字段在哪里,这个awk都应该工作,并且也可以在转义引号上工作

awk '{while(match($0,/"[^"]+",|([^,]+(,|$))/,a)){
      $0=substr($0,RSTART+RLENGTH);b[++x]=a[0]}
      print b[1] b[4];x=0}' file

输入 输出
它甚至可以在

field1,"field,2","but this field has ""escaped"\" quotes",field4
强大的FPAT变量无法启动


解释 启动一个while循环,只要匹配成功(即有一个字段),该循环就会继续。
匹配匹配第一次出现的正则表达式,该正则表达式偶然匹配字段并将其存储在数组
a

 $0=substr($0,RSTART+RLENGTH);b[++x]=a[0]
$0
设置为从匹配字段的末尾开始,并将匹配字段添加到
b
中相应的数组位置

  print b[1] b[4];x=0}
b
打印所需的字段,并在下一行将x设置回零


瑕疵 如果字段同时包含转义引号和逗号,则将失败


编辑 已更新以支持空字段

awk '{while(match($0,/("[^"]+",|[^,]*,|([^,]+$))/,a)){
     $0=substr($0,RSTART+RLENGTH);b[++x]=a[0]}
     print b[1] b[4];x=0}' file
从GNU awk手册():


请参阅,以了解更一般地解析字段中包含换行符等的CSV。

如果字段为空,也会失败,例如
foo、
@EdMorton fixed?,我认为?看起来更好。现在,当设置
x=0
时,您需要添加
delete b
,或者当当前记录的字段较少时,
b
将保留上一条记录结尾的内容,例如,
print b[3]
用于输入行
a、b、c
,后面紧跟
d,e
将输出
c
两次。@EdMorton Yeah oi想到了这一点,但考虑到OP的问题,我认为总会有第四个字段。CSVKit太棒了!感谢您向我介绍:)在MacOS上,我成功地使用brew安装,而不是Pip。我很好奇内部会发生什么?在perl中使用正则表达式可以产生非常不同的匹配:
perl-lnE'while(/([^,]*)|(“[^”]+”)/g{say“#$1#”}@rubystallion您必须询问gawk开发人员内部发生了什么,但正则表达式通常匹配最左边的字符串,因此事实上perl是在
上匹配的。”1234一条漂亮的街道
NE“
作为两个单独的字符串,加上上面的awk和
grep-Eo'([^,]*)|(“[^”]+”)感谢grep示例,似乎是错误的,这让我找到了答案:上面说:
如果模式允许可变数量的匹配字符,那么从该点开始有多个这样的序列,那么匹配的序列最长。例如,BRE“bb*”匹配字符串“abbbc”的第二到第四个字符,而ERE“(wee | week)(knights | night)”匹配字符串“weeknights”的所有十个字符。
而say
选项是从左到右尝试的,因此找到的第一个选项与整个表达式匹配,是被选中的那个。这意味着替代品不一定是贪婪的。例如:当匹配“foo | foot”和“barefoot”时,只有“foo”部分会匹配,因为这是第一个尝试的备选方案,并且它成功地匹配了目标字符串。
@RalphCallaway Right,FPAT是GNU awk扩展。安装gawk或查看我的答案中的链接,以获得适用于任何awk的解决方案。
 while(match($0,/"[^"]+",|([^,]+(,|$))/,a))
 $0=substr($0,RSTART+RLENGTH);b[++x]=a[0]
  print b[1] b[4];x=0}
awk '{while(match($0,/("[^"]+",|[^,]*,|([^,]+$))/,a)){
     $0=substr($0,RSTART+RLENGTH);b[++x]=a[0]}
     print b[1] b[4];x=0}' file
$ awk -vFPAT='([^,]*)|("[^"]+")' -vOFS=, '{print $1,$4}' file
"abc@xyz.com,www.example.com",field4
"def@xyz.com",field4