【问题标题】:sed and regex to replace ',' except inside a stringsed 和正则表达式替换 ',' 字符串内除外
【发布时间】:2014-02-09 04:23:01
【问题描述】:

我有以下架构的输入

10,0,'string1_string2,_string3','',8,0,0,0.59,'20140101205216','20140128074836',584266915,5934

我想使用 sed 将所有逗号“,”字符替换为制表符。约束是不替换文本字符串中的“,”(即,'string1_string2,_string3' 中的逗号不应替换为制表符)。执行此操作的正则表达式是 ,(?!,_)。

但是下面的 sed 不起作用。我也尝试过所有转义排列。

sed s/",\(\?\!,_\)"/"\t"/g 

有没有办法做到这一点?

【问题讨论】:

  • 我会使用 Perl 程序来读取该行,根据 CSV 字段将其分解,修改相关字段,然后重建该行。以非结构化方式处理结构化数据是一场长期的噩梦。
  • 感谢 Johanthan 和下面的其他人。是的,你说得很对。我做过很多噩梦。按照你的建议,我有一个解决方案。

标签: regex sed


【解决方案1】:

在 Mac OS X 10.9.1 上,您可以使用:

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

除非您将 X 替换为实际的选项卡。对于您的输入行,这会产生:

10X0X'string1_string2,_string3'X''X8X0X0X0.59X'20140101205216'X'20140128074836'X584266915X5934

在你想要标签的地方有 X。使用 GNU sed,您可以使用 -r 代替 -E(尽管它也可以识别 -E)。 Mac sed 不会将\t 扩展为选项卡; GNU sed 将。使用 Bash,您可以使用 ANSI-C 引用机制让 shell 在传递给 sed 的字符串中嵌入一个制表符:

sed -E -e "s/('[^']*'|[^,]*),/\1"$'\t'"/g"

没有扩展正则表达式(由-r-E 激活),在sed 中不值得尝试;请改用awk

正则表达式查找 either 一个单引号,后跟零个或多个非引号和一个单引号 零个或多个非逗号,后跟一个逗号,并将其替换为记忆中的非此即彼字符串和“选项卡”(使用 X 表示选项卡,因为它更明显)。


devnullpoints out 上面的答案替换了行尾字符串中的逗号。有一个解决方法:

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

分号前的s///g在每行末尾添加一个制表符;分号后的s/// 删除了刚刚添加的选项卡。

【讨论】:

  • +1;在 OSX 上,拼接$'\t' 以创建一个制表符。 (与其他控制字符类似地工作。例如,$'\n')。它看起来很尴尬(没有 awk 双关语的意思),但它有效:sed -E -e "s/('[^']*'|[^,]*),/\1"$'\t'"/g"
  • 是的,使用 Bash 和 ANSI-C Quoting 机制当然可以,但它是处理 \t 而不是 sed 的 shell。 (我知道你知道;我确保以后阅读的其他人也知道。)使用 control-V control-I(或选项卡)也可以。使用 GNU sedsed 本身会处理 \t 到选项卡的转换。
  • 我担心如果字符串以引用文本结尾,这可能会中断,例如a,'b,c'
  • @JonathanLeffler:说得好 - 感谢您向我介绍$'...' 功能的名称
  • 对,这个用perl或者awk比较容易处理。
【解决方案2】:

你可以使用Text::ParseWords:

perl -MText::ParseWords -n -l -e 'print join("\t", parse_line(",", 1, $_));' filename

对于您的输入,它会导致:

10      0       'string1_string2,_string3'      ''      8       0       0       0.59    '20140101205216'        '20140128074836'        584266915       5934

【讨论】:

    【解决方案3】:

    如果可以的话,我会建议 Perl 的帮助,因为 lookarounds 的可用性:

    s="10,0,'string1_string2,_string3','',8,0,0,0.59,'20140101205216','20140128074836',584266915,5934"
    
    perl -pe "s/,(?=(([^']*'){2})*[^']*$)/\t/g" <<< "$s"
    
    10\t0\t'string1_string2,_string3'\t''\t8\t0\t0\t0.59\t'20140101205216'\t'20140128074836'\t584266915\t5934
    

    PS:显示\t 只是为了便于阅读。

    【讨论】:

    • +1;它有效,但我的大脑仍然因试图理解而受到伤害: - 由于前瞻断言(?=...),匹配是通过 every @ 行的 end 执行的987654324@ 找到。 - 括号中的整个表达式是前瞻表达式,仅当前面的 , 不在单引号字符串内时才匹配。 - 它通过查找 pairs 引号来实现 - 这意味着如果行上的剩余引号(如果有的话)未配对,则手头的 , 必须在 inside 带引号的字符串。 - 净效应:仅, 字符。引用字符串之外的字符串被匹配和替换。
    • 是的,它看起来有点棘手,但它的作用是确保逗号后面总是有偶数个单引号(0,2,4,6...)。前瞻就是这样做的(参见{2} 部分)。
    【解决方案4】:

    如果我正确理解您的问题,这似乎可行:

    sed -E 's/,([^_])/\t\1/g'
    

    输出:

    10  0   'string1_string2,_string3'  ''  8   0   0   0.59    '20140101205216'    '20140128074836'    584266915   5934
    

    【讨论】:

    • 这适用于给定的数据,因为巧合的是字符串中的逗号后面跟着下划线,而其他逗号后面都没有下划线。它不能很好地处理变体,例如'string1, string2, string3',_abc_
    • @JonathanLeffler 我知道,但不需要处理问题中的所有变体。 Peyman 建议 sed s/",\(\?\!,_\)"/"\t"/g 替换 ,_ -> \t。我有点困惑他真正要求的是什么。如果它应该适用于所有其他变体,那么我的回答当然是没用的。
    猜你喜欢
    • 2012-12-13
    • 1970-01-01
    • 2016-08-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-03
    • 2021-11-20
    • 1970-01-01
    相关资源
    最近更新 更多