仅在逗号分隔文件中删除引号之间的逗号


23

我有一个用逗号(,)分隔的输入文件。有一些用双引号引起来的字段,其中用逗号分隔。这是示例行

123,"ABC, DEV 23",345,534.202,NAME

我需要删除所有在双引号和双引号内出现的逗号。所以上面的行应该解析成如下图所示

123,ABC DEV 23,345,534.202,NAME

我尝试使用以下方法sed,但未给出预期的结果。

sed -e 's/\(".*\),\(".*\)/\1 \2/g'

任何快速的运用了sedawk或者任何其他Unix工具吗?


我不确定您要做什么,但实用程序“ csvtool”解析sv的能力要比sed或awk等通用工具好得多。几乎在每个Linux发行版中都有。
figtrap

Answers:


32

如果引号是平衡的,您将要删除每两个引号之间的逗号,可以这样表示awk

awk -F'"' -v OFS='' '{ for (i=2; i<=NF; i+=2) gsub(",", "", $i) } 1' infile

输出:

123,ABC DEV 23,345,534.202,NAME

说明

-F"品牌在awk分离的双引号标志线,这意味着所有其他领域将是引号之间的文本。for循环gsub在所有其他字段上运行,是全局替换的缩写,用逗号(",")替换为("")。所述1在端调用默认代码块:{ print $0 }


1
请您能详细gsub解释一下,这支班轮是如何工作的?请。
mtk 2012年

谢谢!这个脚本确实很好用,但是您能在脚本结尾解释孤独的1吗?- } -1' -
CocoaEv

@CocoaEv:它执行{ print $0 }。我也将其添加到说明中。
2014年

2
这种方法有一个问题:有时csv的行跨越多行,例如:(prefix,"something,otherthing[newline]something , else[newline]3rdline,and,things",suffix 即:多行,并在多行双引号内的任何位置嵌套“,”:"...."应该重新连接整个部分,并且内部,应为替换/删除...):在这种情况下,脚本不会看到双双引号,而且解决起来也不是那么容易(需要“重新加入”处于“开放”状态(即,奇数)的行)双引号... +格外小心,如果还有一个逃脱\" 的字符串)
奥利维尔·杜拉克

1
喜欢这个解决方案,但鉴于我经常喜欢保留逗号但仍想划定界线,因此我对其进行了调整。相反,我将引号的逗号切换为管道,将csv转换为psv文件:awk -F'"' -v OFS='"' '{ for (I=1; i<=NF; i+=2) gsub(",", "|", $i) } 1' infile
Danton Noriega,

7

只需一次使用sed和一个循环,就会获得良好的响应:

echo '123,"ABC, DEV 23",345,534,"some more, comma-separated, words",202,NAME'|
  sed ':a;s/^\(\([^"]*,\?\|"[^",]*",\?\)*"[^",]*\),/\1 /;ta'
123,"ABC  DEV 23",345,534,"some more  comma-separated  words",202,NAME

说明:

  • :a; 是分支的标签
  • s/^\(\([^"]*,\?\|"[^",]*",\?\)*"[^",]*\),/\1 / 可能包含3个封闭部分
    • 首先是第二个:[^"]*,\?\|"[^",]*",\?匹配不包含双引号的字符串,然后可以跟一个逗号,或者用两个双引号括起来的字符串,不带逗号,然后跟一个逗号。
    • 第一RE部分由前面部分2的多次重复组成,后跟一个双引号和一些特征,但没有双引号,也没有昏迷。
    • 第一个RE部分,然后是昏迷。
    • 注意,不需要触摸其余部分
  • ta:a如果先前的s/命令做了一些更改,将循环到。

还可与嵌套引号一起使用。很好,谢谢!
tricasse

5

能够同时处理平衡引号之间的多个逗号的通用解决方案需要嵌套替换。我在perl中实现了一个解决方案,该解决方案处理给定输入的每一行,并且仅在每两对引号中替换逗号:

perl -pe 's/ "  (.+?  [^\\])  "               # find all non escaped 
                                              # quoting pairs
                                              # in a non-greedy way

           / ($ret = $1) =~ (s#,##g);         # remove all commas within quotes
             $ret                             # substitute the substitution :)
           /gex'

或总之

perl -pe 's/"(.+?[^\\])"/($ret = $1) =~ (s#,##g); $ret/ge'

您可以将要处理的文本通过管道传输到命令,也可以将要处理的文本文件指定为最后一个命令行参数。


1
[^\\]将不得不匹配引号内的最后一个字符,并删除它(非\字符),即的不良影响,你不应该消耗该字符。试试吧(?<!\\)
tojrobinson

感谢您的反对,我已予以纠正。尽管如此,我认为我们不需要在这里断言,还是我们!?
user1146332 2012年

1
将非\包含在捕获组中会产生相同的结果。+1
tojrobinson

1
+1。在使用sed尝试了几件事之后,我检查了sed的文档,并确认它不能仅对行的匹配部分应用替换...因此放弃并尝试了perl。结束了一个非常类似的方法,但这个版本使用[^"]*进行匹配非贪婪(即一切与从一个"下一个 "perl -pe 's/"([^"]+)"/($match = $1) =~ (s:,::g);$match;/ge;'。它不承认一个奇怪的想法,即引号可能会以反斜杠转义:-)
cas

谢谢你的评论。如果该[^"]*方法或显式非贪婪方法消耗更少的cpu时间,将会很有趣。
user1146332 2012年

3

我会使用具有适当CSV解析器的语言。例如:

ruby -r csv -ne '
  CSV.parse($_) do |row|
    newrow = CSV::Row.new [], []
    row.each {|field| newrow << field.delete(",")}
    puts newrow.to_csv
  end
' < input_file

虽然我最初很喜欢这种解决方案,但对于大文件却发现它的速度
实在

3

您的第二个引号放错了位置:

sed -e 's/\(".*\),\(.*"\)/\1 \2/g'

此外,使用正则表达式倾向于匹配文本的最长部分,这意味着如果字符串中有多个引用字段,则此操作将无效。

处理sed中多个引用字段的方法

sed -e 's/\(\"[^",]\+\),\([^",]*\)/\1 \2/g' -e 's/\"//g'

这也是解决此问题的一种方法,但是,对于每个引用的字段可能包含多个逗号的输入,sed中的第一个表达式将必须重复单个字段中最大逗号内容的次数,或者直到它为止。完全不改变输出。

用一个以上的表达式运行sed应该比运行多个sed进程和使用全管道运行的“ tr”效率更高。

但是,如果输入格式不正确,则可能会产生不良后果。即嵌套引号,无终止引号。

使用正在运行的示例:

echo '123,"ABC, DEV 23",345,534,"some more, comma-separated, words",202,NAME' \
| sed -e 's/\(\"[^",]\+\),\([^",]*\)/\1 \2/g' \
-e 's/\(\"[^",]\+\),\([^",]*\)/\1 \2/g' -e 's/\"//g'

输出:

123,ABC  DEV 23,345,534,some more  comma-separated  words,202,NAME

您可以通过条件分支使其更通用,并通过ERE(例如GNU sed :)更易读sed -r ':r; s/("[^",]+),([^",]*)/\1 \2/g; tr; s/"//g'
2013年

2

在perl中-您可以使用它Text::CSV来解析它,并轻松完成它:

#!/usr/bin/env perl
use strict;
use warnings;

use Text::CSV; 

my $csv = Text::CSV -> new();

while ( my $row = $csv -> getline ( \*STDIN ) ) {
    #remove commas in each field in the row
    $_ =~ s/,//g for @$row;
    #print it - use print and join, rather than csv output because quotes. 
    print join ( ",", @$row ),"\n";
}

您可以使用进行打印,Text::CSV但是如果这样做,它往往会保留引号。(尽管如此,我建议- 您可以首先解析using ,而不是为输出除去引号Text::CSV)。


0

我创建了一个函数来遍历字符串中的每个字符。
如果字符是引号,则将支票(b_in_qt)标记为true。
当b_in_qt为true时,所有逗号都替换为一个空格。
找到下一个逗号时,b_in_qt设置为false。

FUNCTION f_replace_c (str_in  VARCHAR2) RETURN VARCHAR2 IS
str_out     varchar2(1000)  := null;
str_chr     varchar2(1)     := null;
b_in_qt     boolean         := false;

BEGIN
    FOR x IN 1..length(str_in) LOOP
      str_chr := substr(str_in,x,1);
      IF str_chr = '"' THEN
        if b_in_qt then
            b_in_qt := false;
        else
            b_in_qt := true;
        end if;
      END IF;
      IF b_in_qt THEN
        if str_chr = ',' then
            str_chr := ' ';
        end if;
      END IF;
    str_out := str_out || str_chr;
    END LOOP;
RETURN str_out;
END;

str_in := f_replace_c ("blue","cat,dog,horse","",yellow,"green")

RESULTS
  "blue","cat dog horse","",yellow,"green"
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.