【问题标题】:Replacing everything between nth occurrences in file替换文件中第 n 次出现之间的所有内容
【发布时间】:2021-09-17 03:08:39
【问题描述】:

我有一个包含如下字段的文件:

2|508|PNP|20-dec-2015 12:32:20|3451101|0|3xPirate Ship Cruise | CPT||0598
2|504|PNP|20-dec-2015 12:32:20|3451101|0|3xPirate Ship Cruise | CPT||0598
2|505|PNP|20-dec-2015 12:32:20|3451101|0|3xPirate Ship Cruise || CPT||0598
2|506|PNP|20-dec-2015 12:32:20|3451101|0|3xPirate Ship Cruise | |CPT||0598

我想得到最终文件:

2|508|PNP|20-dec-2015 12:32:20|3451101|0|3xPirate Ship Cruise  CPT||0598
2|504|PNP|20-dec-2015 12:32:20|3451101|0|3xPirate Ship Cruise  CPT||0598
2|505|PNP|20-dec-2015 12:32:20|3451101|0|3xPirate Ship Cruise  CPT||0598
2|506|PNP|20-dec-2015 12:32:20|3451101|0|3xPirate Ship Cruise  CPT||0598

我尝试了以下方法:

sed 's/|//7'

这很棒,因为它删除了不需要的 |分隔符,但是,在第 7 字段中,数据有时在第 7 字段中有多个管道,我的代码在第一次运行时没有发现。

有没有办法使用 sed、awk 或 python 删除一个或多个 |在第 7 场使总 |管道总共只有 8 个 |?

【问题讨论】:

  • 您真的应该尝试修复生成输入文件的任何内容,以便它引用任何可以包含| 的字段,因此将是有效的 CSV 格式,例如2|508|PNP|20-dec-2015 12:32:20|3451101|0|"3xPirate Ship Cruise | CPT"||0598,以后就不用玩这种游戏了。
  • 你真的想要CruiseCPT 之间的两个空格吗?或者这只是一个错字?
  • Roco,请检查my answer 并告诉我它是否有效。
  • 嗨@dawg这是我的错字
  • 嗨@WiktorStribiżew,谢谢你的回复,我会测试你的解决方案并检查结果

标签: python awk sed


【解决方案1】:

你可以使用

sed 's/|[ |]*//7'

|[ |]* 是匹配的 POSIX BRE 模式

  • | - 一个管道字符
  • [ |]* - 零个或多个空格或管道字符(您也可以使用 [[:blank:]|]* 匹配任何水平空格或管道字符)。

online demo

#!/bin/bash
s='2|508|PNP|20-dec-2015 12:32:20|3451101|0|3xPirate Ship Cruise | CPT||0598
2|504|PNP|20-dec-2015 12:32:20|3451101|0|3xPirate Ship Cruise | CPT||0598
2|505|PNP|20-dec-2015 12:32:20|3451101|0|3xPirate Ship Cruise || CPT||0598
2|506|PNP|20-dec-2015 12:32:20|3451101|0|3xPirate Ship Cruise | |CPT||0598'
sed 's/|[ |]*//7' <<< "$s"

输出:

2|508|PNP|20-dec-2015 12:32:20|3451101|0|3xPirate Ship Cruise CPT||0598
2|504|PNP|20-dec-2015 12:32:20|3451101|0|3xPirate Ship Cruise CPT||0598
2|505|PNP|20-dec-2015 12:32:20|3451101|0|3xPirate Ship Cruise CPT||0598
2|506|PNP|20-dec-2015 12:32:20|3451101|0|3xPirate Ship Cruise CPT||0598

如果您需要匹配到第七个管道字符,然后匹配连续的空格和管道并删除所有管道但保留空格,Perl 解决方案可能更合适:

perl -pe 's{^(?:[^|]*\|){6}[^|]*\K\|[\s|]*}{$&=~s/\|//gr}e' file > newfile

this online demo。它的作用是

  • ^(?:[^|]*\|){6}[^|]*\K\|[\s|]* 匹配六次出现的零个或多个字符而不是 |,然后匹配一个 | 字符,然后再匹配零个或多个字符而不是管道(使用 ^(?:[^|]*\|){6}[^|]*),\K 省略匹配的文本和 @ 987654336@ 匹配并消耗一个管道字符,然后是任意数量的管道和空白字符
  • 感谢e 标志,RHS(替换)被视为 Perl 表达式,并且
  • $&amp;=~s/\|//gr 表示从匹配值中删除所有管道(g 表示多次出现)。

【讨论】:

  • 除非我的眼睛欺骗了我,否则应该保留第 7 根管道两侧的空间,也许 sed 's/|[ |]*/ /7' file
  • @potong 这可能不是 OP 想要的,但是,我为这种情况添加了一个 perl 解决方案,因为它是一个简短而简单的单行。
【解决方案2】:
$ awk 'BEGIN{FS=" *[|] *"; OFS="|"} {print $1, $2, $3, $4, $5, $6, $7 " " $(NF-2), $(NF-1), $NF}' file
2|508|PNP|20-dec-2015 12:32:20|3451101|0|3xPirate Ship Cruise CPT||0598
2|504|PNP|20-dec-2015 12:32:20|3451101|0|3xPirate Ship Cruise CPT||0598
2|505|PNP|20-dec-2015 12:32:20|3451101|0|3xPirate Ship Cruise CPT||0598
2|506|PNP|20-dec-2015 12:32:20|3451101|0|3xPirate Ship Cruise CPT||0598

【讨论】:

    【解决方案3】:

    使用这个 Perl 单行代码:

    perl -F'\s*\|\s*' -lane 'print join "|", @F[0..5], ( join " ", grep { /\S/ } @F[6..($#F-2)]),  @F[-2, -1];' in.txt > out.txt
    

    输出:

    2|508|PNP|20-dec-2015 12:32:20|3451101|0|3xPirate Ship Cruise CPT||0598
    2|504|PNP|20-dec-2015 12:32:20|3451101|0|3xPirate Ship Cruise CPT||0598
    2|505|PNP|20-dec-2015 12:32:20|3451101|0|3xPirate Ship Cruise CPT||0598
    2|506|PNP|20-dec-2015 12:32:20|3451101|0|3xPirate Ship Cruise CPT||0598
    

    Perl 单行程序使用这些命令行标志:
    -e:告诉 Perl 查找内联代码,而不是在文件中。
    -n:循环输入一行一次,默认将其分配给$_
    -l:在执行内联代码之前剥离输入行分隔符(默认为 *NIX 上的"\n"),并在打印时附加它。-a :将$_ 拆分为数组@F 上的空格,或者,如果提供,则在-F 选项中指定的正则表达式上。
    -F'\s*\|\s*' :在文字管道上拆分为@F,可选被 0 个或多个空白字符包围。

    @F[0..5]:输入行的字段 0 到 5(前 6 个字段,字段索引从 0 开始)。
    join " ", grep { /\S/ } @F[6..($#F-2)]):从 6 到结尾的字段,除了最后 2 个字段,选择从这些使用grep 仅具有至少一个非空白字符(\S)的字段,然后将它们在空格中连接成一个字符串。
    @F[-2, -1]:输入行的最后两个字段。

    另请参阅:
    perldoc perlrun: how to execute the Perl interpreter: command line switches
    perldoc perlre: Perl regular expressions (regexes)

    【讨论】:

      【解决方案4】:

      也许是这样

      awk 'BEGIN {FS="|";OFS=""} {for (i=1;i<NF;++i) if (i<7||NF-3<i) $i=$i "|"}1' file
      

      sed ':a;s/|/&/9;t x;b;:x;s///7;t a' file
      

      【讨论】:

      • 谢谢@rowboat,我发现您的解决方案能够最好地删除脏列(字段),因为它足够强大,可以删除许多不需要的管道。
      【解决方案5】:

      另一个perl:

      perl -lnE 'say join("  ",split(/(?: \| ?\|? ?)/,$_, 2))' file
      

      或者如果你想用一个轻量级的 CSV 解析器来处理它,你可以使用ruby

      ruby -r csv -lne '
          BEGIN{ options={:col_sep=>"|"} }
          CSV.parse($_, **options){ |r| 
             puts r[0..6].join("|")+" "+r[-3..-1].join("|").lstrip}
      ' <<< "$s"
      

      或 sed:

      sed -E 's/ \|[ |][ |]?/  /' <<< "$s"
      

      任何印刷品:

      2|508|PNP|20-dec-2015 12:32:20|3451101|0|3xPirate Ship Cruise  CPT||0598
      2|504|PNP|20-dec-2015 12:32:20|3451101|0|3xPirate Ship Cruise  CPT||0598
      2|505|PNP|20-dec-2015 12:32:20|3451101|0|3xPirate Ship Cruise  CPT||0598
      2|506|PNP|20-dec-2015 12:32:20|3451101|0|3xPirate Ship Cruise  CPT||0598
      

      注意:

      在您的示例中,这些复制了 CruiseCPT 之间的两个空格。如果您不希望这样,请删除 +" " 加入 ruby​​ 并在 perl 中将 " " 更改为 " "

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-08-31
        • 1970-01-01
        • 2021-12-19
        • 2014-03-31
        • 1970-01-01
        • 2018-09-08
        • 2021-04-23
        相关资源
        最近更新 更多