perl 编辑文件中的最后一个示例

insrf1ej  于 2022-11-24  发布在  Perl
关注(0)|答案(6)|浏览(143)

我有一个巨大的文本文件(~ 1. 5GB),其中有许多行以“.Ends”结尾。
我需要一个linux oneliner(perl\ awk\ sed)来找到最后的地方'.Ends'出现在文件中,并在它之前添加几行。
我尝试使用tac两次,但遇到了Perl:
当我用途:
tac ../../test | perl -pi -e 'BEGIN {$flag = 1} if ($flag==1 && /.Ends/) {$flag = 0 ; print "someline\n"}' | tac
它首先打印“someline\n”,然后才打印。Ends结果为:
...
.结束
索梅林
当我用途:
tac ../../test | perl -e 'BEGIN {$flag = 1} print ; if ($flag==1 && /.Ends/) {$flag = 0 ; print "someline\n"}' | tac
它什么都不打印。
当我用途:
tac ../../test | perl -p -e 'BEGIN {$flag = 1} print $_ ; if ($flag==1 && /.Ends/) {$flag = 0 ; print "someline\n"}' | tac
它会打印所有内容两次:
...
.结束
索梅林
.结束
是否有一种流畅的方法来执行此编辑?
不必跟我解方向,我不挑剔......
奖金-如果行可以来自不同的文件,这将是伟大的(但真的不是必须的)

  • 编辑 *

测试输入文件:

gla2 
fla3 
dla4 
rfa5 
.Ends
shu
sha
she
.Ends
res
pes
ges
.Ends  
--->
...
pes
ges
someline
.Ends
# * some irrelevant junk * #
r1zk6ea1

r1zk6ea11#

假设该短语的最后一个示例位于文件的最下面,那么从后面处理文件(例如使用File::ReadBackwards)将极大地提高性能。
由于您需要在最后一个标记之前添加其他文本到文件中,因此我们必须复制它的其余部分,以便能够在添加后将其放回。

use warnings;
use strict;
use feature 'say';
use Path::Tiny;
use File::ReadBackwards;
    
my $file = shift // die "Usage: $0 file\n"; 

my $bw = File::ReadBackwards->new($file);

my @rest_after_marker; 

while ( my $line = $bw->readline ) { 
    unshift @rest_after_marker, $line;
    last if $line =~ /\.Ends/;
}
# Position after which to add text and copy back the rest
my $pos = $bw->tell;    
$bw->close;

open my $fh, '+<', $file or die $!;    
seek $fh, $pos, 0;
truncate $fh, $pos;    
print $fh $_ for path("add.txt")->slurp, @rest_after_marker;

在最后一个.Ends之前添加的新文本可能在文件add.txt中。
问题是最后一个.Ends标记之后还有多少文件?我们将所有文件复制到内存中,以便能够将其写回。如果太多,则将其复制到临时文件而不是内存中,然后从那里使用并删除该文件。

eanckbw9

eanckbw92#

使用GNU sed-i.bak将创建一个扩展名为.bak的备份文件,同时就地保存原始文件

$ sed -Ezi.bak 's/(.*)(\.Ends)/\1newline\nnewline\n\2/' input_file
$ cat input_file
gla2
fla3
dla4
rfa5
.Ends
shu
sha
she
.Ends
res
pes
ges
.Ends
--->
...
pes
ges
someline
newline
newline
.Ends
tjrkku2a

tjrkku2a3#

输入:

$ cat test.dat
dla4
.Ends
she
.Ends
res
.Ends
abc

$ cat new.dat
newline 111
newline 222

一个与OP的tac | <process> | tac方法一致的awk想法:

$ tac test.dat | awk -v new_dat="new.dat" '1;/\.Ends/ && !(seen++) {system("tac " new_dat)}' | tac
dla4
.Ends
she
.Ends
res
newline 111
newline 222
.Ends
abc

awk的另一个想法是用输入文件的双遍调用代替双tac调用:

$ awk -v new_dat="new.dat" 'FNR==NR { if ($0 ~ /\.Ends/) lastline=FNR; next} FNR==lastline { system("cat "new_dat) }; 1' test.dat test.dat
dla4
.Ends
she
.Ends
res
newline 111
newline 222
.Ends
abc

备注:

  • 这两种解决方案都将修改后的数据写入stdout(与OP的当前代码所做的相同)
  • 这些解决方案都不会修改原始输入文件(test.dat
agxfikkp

agxfikkp4#

由于您要从文件中读取新行:
第一个
上面假设在你的示例输入中的某些行.Ends后面的白色是错误的。如果它们确实存在,那么将$0==".Ends"更改为/^\.Ends[[:space:]]*$/,或者如果在这些行上也可能有前导空格,则将/^[[:space:]]*\.Ends[[:space:]]*$/更改为/\.Ends/,或者如果在.Ends之前/之后可以有任何字符,则将/\.Ends/更改为.Ends

pb3skfrl

pb3skfrl5#

输入:

$ cat test.dat
dla4
.Ends
she
.Ends
res
.Ends
abc

$ cat new.dat
newline 111
newline 222

一种ed方法:

$ ed test.dat >/dev/null 2>&1 <<EOF
1
?.Ends
-1r new.dat
wq
EOF

或者作为一句俏皮话:

$ ed test.dat < <(printf '%s\n' 1 ?.Ends '-1r new.dat' wq) >/dev/null 2>&1

其中:

  • >/dev/null 2>&1-强力抑制诊断和信息消息
  • 1-转到第1行
  • ?.Ends-在文件中向后搜索字符串.Ends(即,在文件中查找最后一个.Ends
  • -1r new.dat-在文件中向后/向上移动1行(-1),并在new.dat的内容中读取r
  • wq-w写入与q退出(也称为保存与退出)

这会产生:

$ cat test.dat
dla4
.Ends
she
.Ends
res
newline 111
newline 222
.Ends
abc

**注意:**与OP的当前代码(将修改的数据写入stdout)不同,此解决方案修改原始输入文件(test.dat

h9vpoimq

h9vpoimq6#

首先让grep进行搜索,然后将awk注入行。

$ cat insert
new content
new content

$ line=$(cat insert)

$ awk -v var="${line}" '
      NR==1{last=$1; next} 
      FNR==last{print var}1' <(grep -n "^\.Ends$" file | cut -f 1 -d : | tail -1) file
rfa5 
.Ends
she
.Ends
ges
.Ends  
ges
new content
new content
.Ends
ges
ges

数据

$ cat file
rfa5 
.Ends
she
.Ends
ges
.Ends  
ges
.Ends
ges
ges

相关问题