替换文件中第 n 次出现之间的所有内容

Posted

技术标签:

【中文标题】替换文件中第 n 次出现之间的所有内容【英文标题】:Replacing everything between nth occurrences in file 【发布时间】: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,谢谢你的回复,我会测试你的解决方案并检查结果 【参考方案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/\|//gre' 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 'BEGINFS=" *[|] *"; 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 switchesperldoc 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 中将 " " 更改为 " "

【讨论】:

以上是关于替换文件中第 n 次出现之间的所有内容的主要内容,如果未能解决你的问题,请参考以下文章

sed / awk 匹配文件中第二次出现的正则表达式,并替换整行

替换字符串中第 n 次出现的子字符串

SQL语句替换某表某字段中第几字符

sed用法

r 替换字符串中第n个出现的值

正则表达式匹配替换第n次出现