替换文件中第 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
,以后就不用玩这种游戏了。
你真的想要Cruise
和CPT
之间的两个空格吗?或者这只是一个错字?
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 表达式,并且
$&=~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
注意:
在您的示例中,这些复制了 Cruise
和 CPT
之间的两个空格。如果您不希望这样,请删除
+" "
加入 ruby 并在 perl 中将 " "
更改为 " "
。
【讨论】:
以上是关于替换文件中第 n 次出现之间的所有内容的主要内容,如果未能解决你的问题,请参考以下文章