我正在编写一个脚本,通过在制表符间距后的文件中的特定字符串下添加一些文本来附加文本文件。在以下情况下,需要帮助在匹配的字符串“apple”之后添加新行和制表符间距。
示例文件:
apple
<tab_spacing>original text1
orange
<tab_spacing>original text2
预期产出:
apple
<tab_spacing>testing
<tab_spacing>original text1
orange
<tab_spacing>original text2
我尝试过的:
use strict;
use warnings;
my $config="filename.txt";
open (CONFIG,"+<$config") or die "Fail to open config file $config\n";
while (<CONFIG>) {
chop;
if (($_ =~ /^$apple$/)){
print CONFIG "\n";
print CONFIG "testing\n";
}
}
close CONFIG;
我们不能像尝试的那样简单地将文本“添加”到文件的中间。文件是一系列字节,不能添加或删除它们(末尾除外),只能更改它们。因此,如果我们开始写入文件的中间部分,那么我们将更改那里的字节,从而覆盖该位置后面的内容。相反,我们必须复制其余文本并在“添加”之后将其写回,或者复制文件在此过程中添加文本。
还有一种方法是将整个文件读入一个字符串并运行一个正则表达式来改变它,然后写出新的字符串。假设文件不是太大
perl -0777 -pe's{apple\n\K(\t)}{Added text\n$1}g' in.txt
-0777
开关使它将整个文件读入一个字符串(“slurp”它),在$_
中可用,正则表达式默认绑定到该字符串。 \K
,即 a lookbehind,会删除之前的匹配项,这样它们就不会从字符串中消耗掉,我们也不必(捕获并)将它们放回去。使用 /g
modifier 它会不断遍历整个字符串,以查找和更改所有出现的模式。
这会将更改后的文件打印到屏幕上,可以通过重定向将其保存在新文件中
perl -0777 -pe'...' in.txt > out.txt
或者,可以使用 -i
更改输入文件“in place”
perl -0777 -i.bak -pe'...' in.txt
.bak
使它保存带有.bak
扩展名的原件。请参阅 perlrun 中的开关。
另一种方法是对后面的内容(选项卡)使用 lookahead 这样我们就不必捕获并放回去
perl -0777 -pe's{apple\n\K(?=\t)}{Added text\n}g' in.txt
所有这些都会产生所需的变化。
Note(“tab_spacing”)
上面的正则表达式假定在带有apple
的行之后的行的开头有一个制表符
character。当我们说“制表符”时,我们指的是一个(制表符)字符。
但实际上可能没有制表符的原因有很多,即使它看起来就像有一个。一个例子:编辑器可能会自动将所有制表符替换为空格。
所以在正则表达式中使用
\s+
(多个空格)而不是\t
可能更安全
s{apple\n\K(\s+)}{Added text\n$1}g
或
s{apple\n\K(?=\s+)}{Added text\n}g
如果这是在一个现有的更大的 Perl 程序中完成的(而不是作为命令行程序,如上所述的“单行程序”),一种方式
use Path::Tiny; # path()
my $file_content = path($file)->slurp; # read the file into a string
# Now use a regex; all discussion above applies
$file_content =~ s{apple\n\K(?=\t)}{Added text\n}g;
# Print out $file_content, to be redirected etc. Or write to a file
path($new_file)->spew($file_content);
我使用库 Path::Tiny 将文件“吞噬”成一个字符串,并使用
spew
将 $file_content
写入新文件。需要安装它,因为它不在“核心”中(通常不会与 Perl 一起安装),如果由于某种奇怪的原因这是一个问题,这里是一个没有任何库的惯用语
my $file_content = do {
local $/;
open my $fh, '<', $file or die "Can't open $file: $!";
<$fh>;
};
甚至
my $file_content = do { local (@ARGV, $/) = $file; <> };
(有关解释和参考,请参阅这篇文章)
老实说,您的代码中有一些非常奇怪的东西:
CONFIG
) 而不是词法变量和双参数 open()
而不是三参数版本 (open my $config_fh, '+<', $config'
) 让我觉得你在使用一些非常古老的 Perl 教程chop()
而不是 chomp()
让我觉得你在使用一些古老的 Perl 教程$
-^$apple$
可能应该是^apple$
此外,Tie::File 已经包含在 Perl 的标准库中已有二十多年了,这将使这项任务变得更加容易。
#!/usr/bin/perl
use strict;
use warnings;
use Tie::File;
tie my @file, 'Tie::File', 'filename.txt' or die $!;
for (0 .. $#file) {
if ($file[$_] eq 'apple') {
splice @file, $_ + 1, 0, "\ttesting\n";
}
}
“制表符间距”的含义并不完全清楚,但您可能正在寻找:
perl -pE 'm/^(\t*)/; say "${1}testing" if $a; $a = /apple/' filename.txt
我怀疑你真的想要
\s
而不是\t
,但是YMMV。基本上,在每一行输入中,您匹配前导空格,然后打印包含该空格的一行,如果前一行匹配,则打印字符串 'testing'。
写得冗长:
#!/usr/bin/env perl
use 5.12.0;
use strict;
use warnings;
my $n = 'filename.txt';
open my $f, '<', $n, or die "$n: $!\n";
while(<$f>){
m/^(\t*)/; # possibly \s is preferred over \t
say "${1}testing" if $a;
$a = /apple/;
print;
}