仅删除 csv 文件中双引号数字中存在的逗号

仅删除 csv 文件中双引号数字中存在的逗号

在文本文件中,我想删除,(逗号)和"(引号)(仅当双引号包含以逗号分隔的数字时)。

56,72,"12,34,54",x,y,"foo,a,b,bar"

预期产出

56,72,123454,x,y,"foo,a,b,bar"

笔记:我仅将上面的行作为示例。我的文本文件包含许多像上面这样的行,双引号内以逗号分隔的数字应该有所不同。那是,

56,72,"12,34,54",x,y,"foo,a,b,bar"
56,92,"12,34",x,y,"foo,a,b,bar"
56,72,"12,34,54,78,76,54,67",x,y,"foo,a,b,bar"
56,72,x,y,"foo,a,b,bar","12,34,54"
56,72,x,y,"foo,a,b,bar","12,34,54","45,57,84,92","bar,foo"

预期输出:

56,72,123454,x,y,"foo,a,b,bar"
56,92,1234,x,y,"foo,a,b,bar"
56,72,12345478765467,x,y,"foo,a,b,bar"
56,72,x,y,"foo,a,b,bar",123454
56,72,x,y,"foo,a,b,bar",123454,45578492,"bar,foo"

双引号内有许多n数字,用逗号分隔。并且保留包含字符的双引号。

我喜欢sed文本处理工具。如果您sed为此 发布任何解决方案,我很高兴。

答案1

如果 perl 没问题,这里有一个简短的(可能是快速的,如果不一定简单:))方法:

perl -pe 's:"(\d[\d,]+)":$1=~y/,//dr:eg' file

e运算符的标志(s:::这只是另一种编写方式s///)导致替换被视为每次都会计算的表达式。该表达式$1从正则表达式(已经缺少引号)中获取捕获并通过删除 ( ) 所有逗号来翻译 ( y///,也可以写为) 它。为了获取翻译字符串的值(而不是翻译的计数),必须使用标志to 。tr////dry

对于那些感觉被 Perl 玷污的人来说,这里是 Python 的等价物。 Python确实不是一个shell单行工具,但有时它可以被诱导进行合作。以下内容可以写成一行(与for循环不同,循环不能如此),但水平滚动使其(甚至更)难以阅读:

python -c '
import re;
import sys;
r=re.compile("\"(\d+(,\d+)*)\"");
all(not sys.stdout.write(r.sub(lambda m:m.group(1).replace(",",""),l))
    for l in sys.stdin)
' < file

答案2

这个(改编自这里)应该做你需要的事情,尽管 @rici 的 Perl 更简单:

$ sed -r ':a;s/(("[0-9,]*",?)*"[0-9,]*),/\1/;ta; s/""/","/g; 
          s/"([0-9]*)",?/\1,/g ' file
56,72,123454,x,y,"foo,a,b,bar"
56,92,1234,x,y,"foo,a,b,bar"
56,72,12345478765467,x,y,"foo,a,b,bar"
56,72,x,y,"foo,a,b,bar",123454,
56,72,x,y,"foo,a,b,bar",123454,45578492,"bar,foo"

解释

  • :a:定义一个名为 的标签a
  • s/(("[0-9,]*",?)*"[0-9,]*),/\1/: 这个需要分解一下
    • 首先,使用这个结构:(foo(bar)), \1will befoobar\2will be bar
    • "[0-9,]*",?:匹配 0 个或多个0-9,,后跟 0 或 1 ,
    • ("[0-9,]*",?)*:匹配以上 0 个或多个。
    • "[0-9,]*:匹配 0 个或多个紧随 a 之后的0-9,"
  • ta;: 返回标签a并再次运行如果替换成功。
  • s/""/","/g;: 后期处理。""用。。。来代替","
  • s/"([0-9]*)",?/\1,/g:删除数字周围的所有引号。

用另一个例子可能更容易理解:

$ echo '"1,2,3,4"' | sed -nr ':a;s/(("[0-9,]*",?)*"[0-9,]*),/\1/;p;ta;'
"1,2,34"
"1,234"
"1234"
"1234"

因此,虽然您可以找到紧接在引号后面且后跟逗号和另一个数字的数字,但请将这两个数字连接在一起并重复该过程,直到不再可能为止。

在这一点上,我认为提及info sed描述高级功能的部分中出现的引用是有用的,例如上面使用的标签(感谢查找 if @Braiam):

在大多数情况下,使用这些命令表明您最好使用“awk”或 Perl 等语言进行编程。

答案3

对于 CSV 数据,我会使用具有真正 CSV 解析器的语言。以 Ruby 为例:

ruby -rcsv -pe '
  row = CSV::parse_line($_).map {|e| e.delete!(",") if e =~ /^[\d,]+$/; e} 
  $_  = CSV::generate_line(row)
' <<END
56,72,"12,34,54",x,y,"foo,a,b,bar"
56,92,"12,34",x,y,"foo,a,b,bar"
56,72,"12,34,54,78,76,54,67",x,y,"foo,a,b,bar"
56,72,x,y,"foo,a,b,bar","12,34,54"
56,72,x,y,"foo,a,b,bar","12,34,54","45,57,84,92","bar,foo"
END
56,72,123454,x,y,"foo,a,b,bar"
56,92,1234,x,y,"foo,a,b,bar"
56,72,12345478765467,x,y,"foo,a,b,bar"
56,72,x,y,"foo,a,b,bar",123454
56,72,x,y,"foo,a,b,bar",123454,45578492,"bar,foo"

答案4

使用(以前称为 Perl_6)

~$ raku -pe 's:g/ \" ~ \" (\d+) ** 2..* % "," /{$0.join}/;'  file

输入示例:

56,72,"12,34,54",x,y,"foo,a,b,bar"
56,92,"12,34",x,y,"foo,a,b,bar"
56,72,"12,34,54,78,76,54,67",x,y,"foo,a,b,bar"
56,72,x,y,"foo,a,b,bar","12,34,54"
56,72,x,y,"foo,a,b,bar","12,34,54","45,57,84,92","bar,foo"

示例输出:

56,72,123454,x,y,"foo,a,b,bar"
56,92,1234,x,y,"foo,a,b,bar"
56,72,12345478765467,x,y,"foo,a,b,bar"
56,72,x,y,"foo,a,b,bar",123454
56,72,x,y,"foo,a,b,bar",123454,45578492,"bar,foo"

Raku 是 Perl 系列中的一种编程语言,具有许多强大的正则表达式功能。请参阅下面的 URL,了解此答案的总体概述:

https://unix.stackexchange.com/a/722570/227738

在上面的代码中,识别了数字并删除了嵌入的逗号。正则表达式利用了以下事实:嵌套结构可以用 Raku 的新 ~ 波形符(嵌套)符号表示,这\" ~ \" [\d+]意味着“一个或多个数字被“双引号”包围。

此外,重复结构%可以用 Raku 的新修改重复结构量词来表示。符号 [\d+] ** 2..* % "," 表示“用,逗号分隔的一个或多个数字,此模式重复** 2..*两次或多次。[如果碰巧有尾随分隔符(例如逗号),在语法中使用 a%%代替]。%

这只是一个开始。带有备用分隔符、嵌入换行符、嵌入逗号、可能为空白字段等的 CSV 文件确实需要由真正的 CSV 解析器(如 RakuText::CSV模块)来处理。有关详细信息,请参阅下面的链接。

https://docs.raku.org/language/regexes
https://raku.land/github:Tux/Text::CSV
https://raku.org

相关内容