【问题标题】:how to replace delimiter when delimiter also present in field data at any place in a file?当分隔符也存在于文件中任何位置的字段数据中时,如何替换分隔符?
【发布时间】:2017-01-22 20:09:04
【问题描述】:

我在 linux 中有输入文件的内容,类似于

"test1",2,2,,0,"Sun,day",Jan
"be,st3",1,0,,0,"Sunday",Feb1
"nest",0,0,,0,"Sunday",Jul
"rest,5",,,,0,"Sunday",Aug

需要像这样的输出

test1|2|2||0|Sun,day|Jan
be,st3|1|0||0|Sunday|Feb1
nest|0|0||0|Sunday|Jul
rest,5||||0|Sunday|Aug

使用 tr 命令将 , 替换为 |它也替换了字段值。我无法理解如何只更改分隔符而不是值。有人可以提供一些指示,我可以使用哪些命令以及如何执行此任务?

【问题讨论】:

  • 你的意思是休息,5 在最后一行...
  • @Jean-FrançoisFabre 谢谢。已更正。

标签: linux csv sed


【解决方案1】:

使用 sed:

$ sed -E ':a; s/^(([^"]*("[^"]*")?)*),/\1|/; ta; s/"//g' file
test1|2|2||0|Sun,day|Jan
be,st3|1|0||0|Sunday|Feb1
nest|0|0||0|Sunday|Jul
rest,5||||0|Sunday|Aug

工作原理

如果, 出现在偶数个" 之后,这会将, 更改为|

  • :a

    这定义了一个标签a

  • s/^(([^"]*("[^"]*")?)*),/\1|/

    从行首 ^ 开始,查找以下任意数量的序列:

    1. `[^"]*`  = zero or more non-quotes
    
    2. `("[^"]*")?`  = pairs of quotes
    
  • ta

    如果之前的s 命令导致替换成功,则跳回标签a 并重试。

  • s/"//g

    在我们用竖线替换所有未加引号的逗号后,我们删除引号。

简化

正如 potong 指出的那样,另一种更简单的解决方案是:

sed -E 's/(([^,"]*("[^"]*")*)*),/\1|/g;s/"//g' file

之所以有效,是因为有两个微妙之处:(1) sed 的正则表达式查找最左边最长的匹配项,以及 (2) 在进行全局 (g) 替换时,不允许后续匹配项与之前的匹配项重叠。考虑到这两个规则,此命令仅在偶数个引号后将 , 替换为 |

【讨论】:

  • 可以缩短为sed -r 's/(([^,"]*("[^"]*")*)*),/\1|/g;s/"//g' file吗?
【解决方案2】:

sed 很难做到这一点。

另一方面,在 python(3.x 版)中,只需几行代码:

import csv

with open("input.csv") as fr:
    with open("output.csv","w",newline='') as fw:  # uncomment for python 3.x
    with open("output.csv","wb") as fw:            # python 2.x only
        cr = csv.reader(fr,delimiter=",")
        cw = csv.writer(fw,delimiter="|")
        cw.writerows(cr)

它是如何工作的:它只是使用了惊人的内置csv 模块。用分隔符读,用另一个写。

好的,现在只是为了好玩......如果你真的想知道,我的sed 解决方案

像这样创建一个 sedfile:

s/"\([^",]\+\)"/\1/g
s/"\([^"]\+\),\([^"]\+\)"/\1%\2/g
s/,/\|/g
s/%/,/g

申请sed -f sedfile.txt input.csv > output.csv

它是如何工作的:

  • 从简单字段中去掉引号
  • 从包含逗号的字段中去掉引号,但用百分号替换逗号
  • 更改分隔符
  • 用逗号改变回百分号

结果:

test1|2|2||0|Sun,day|Jan
be,st3|1|0||0|Sunday|Feb1
nest|0|0||0|Sunday|Jul
rest,5||||0|Sunday|Aug

每个受保护字段限制为 1 个逗号(可以扩展到 3 个或更多...),并且字段不得使用 % 符号。

【讨论】:

  • Python 2.6.6 ?谢谢
【解决方案3】:
$ cat ip.txt 
"test1",2,2,,0,"Sun,day",Jan
"be,st3",1,0,,0,"Sunday",Feb1
"nest",0,0,,0,"Sunday",Jul
"rest,5",,,,0,"Sunday",Aug
xyz,"12,234","a,b","abc",893,23,"Sep"

$ perl -pe 's/"(?<a>[^"]+)",|(?<a>[^,]*),/$+{a}|/g' ip.txt 
test1|2|2||0|Sun,day|Jan
be,st3|1|0||0|Sunday|Feb1
nest|0|0||0|Sunday|Jul
rest,5||||0|Sunday|Aug
xyz|12,234|a,b|abc|893|23|"Sep"
  • "(?&lt;a&gt;[^"]+)", "" 内的文本(紧随其后的是 ,)在命名组 a 内被捕获
  • (?&lt;a&gt;[^,]*),, 以外的以 , 结尾的文本也被捕获在命名组 a
  • 可以通过哈希%+ 访问命名组
  • 顺序很重要 - "(?&lt;a&gt;[^"]+)", 在评估备用正则表达式之前首先完成
  • 注意:如果最后一个元素被引用,比如"Sep",引号将不会被删除


进一步阅读: perlre - 搜索 named capture

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-01-09
    • 1970-01-01
    • 2021-11-10
    • 1970-01-01
    • 1970-01-01
    • 2015-01-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多