Bash 如何使awk忽略双引号内的字段分隔符?
我需要删除逗号分隔值文件中的两列。 考虑CSV文件中的以下行: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}' 但是引号中的嵌入
"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
如果当前字段以双引号结尾,则会将字段计数器增加2。这就是我们跳过字段2和字段3的方式if($i~/“$/)i=i+2
完成print”“
循环后,将打印一条换行符for
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