我有2个文件。
我想在input.txt中的mapping.txt中找到键的每一个匹配项,并将其替换为与该键对应的值。
请注意,我希望每次成功匹配时都会覆盖input.txt中该行的内容。
我写了以下代码:
#! /usr/bin/perl
use strict;
use warnings;
(my $mapping,my $input)=@ARGV;
open(MAPPING,'<',$mapping) || die("couldn't read from the file, $mapping with error: $!\n");
while(<MAPPING>)
{
chomp $_;
my $line=$_;
(my $key,my $value)=split("=",$line);
open(INPUT,'+<',$input);
while(<INPUT>)
{
chomp $_;
if(index($_,$key)!=-1)
{
$_=~s/\Q$key/$value/g;
# move pointer to beginning of line
print INPUT $_."\n";
}
}
close INPUT;
}
close MAPPING;
简要概述代码:
如果我可以将文件指针移动到行的开头,那么我可以用以下内容覆盖:
打印INPUT $ _,“\ n”
完成后,代码将关闭文件。它将从mapping.txt中选择下一个键值对,并再次扫描输入文件,从开始查找匹配并替换它们。
最重要的一点是,每次内部while循环都将在input.txt上运行,而input.txt在内部while循环的前一次迭代中被修改。这样,任何成功的查找和替换操作都将继续保存在input.txt文件中。
我该怎么做?
感谢。
答案 0 :(得分:3)
首先,您应该使用词法文件句柄,open
的三参数形式,并始终检查状态以确保open
成功(与映射文件一样)但不是输入文件。)
您建议的解决方案是在使用print
之前倒回到行的开头将无法正常工作,因为您无法更新文件的一部分,除非您的替换数据完全相同的大小作为它正在取代的数据。在您的情况下通常不会这样。
有许多解决方案,第一个也是最简单的方法是反转循环并将映射文件的读取循环放在输入文件的读取循环中。你的代码看起来像这样:
use strict;
use warnings;
my ($mapping, $input) = @ARGV;
open my $infh, '<', $input or die "Unable to open '$input': $!";
while (my $line = <$input>) {
open my $mapfh, '<', $mapping or die "Unable to open '$mapping': $!";
while (<$mapfh>) {
chomp;
my ($key, $value) = split /=/;
$line =~ s/\Q$key/$value/g;
}
print $line;
}
但是你的输出被发送到STDOUT,你必须安排输出保存到文件并适当地重命名。
此处的替代方法是使用-I
命令行选项,该选项强制自动重命名文件,并在需要时保存备份。使用裸-I
将通过删除旧文件并重命名新输出来就地修改文件,同时为参数提供类似-I.bak
的值将通过附加.bak
重命名旧文件而不是删除它。 -I
选项仅适用于使用空<>
运算符从ARGV读取的文件,并将内置变量$^I
设置为值(或空字符串''
)具有相同的效果。代码如下所示:
use strict;
use warnings;
my $mapping = shift @ARGV;
$^I = '.bak';
while (my $line = <>) {
open my $mapfh, '<', $mapping or die "Unable to open '$mapping': $!";
while (<$mapfh>) {
chomp;
my ($key, $value) = split /=/;
$line =~ s/\Q$key/$value/g;
}
print $line;
}
第三种更简洁的替代方法是使用Tie::File
,它将Perl数组映射到文件内容,并将数组的所有修改反映回原始文件。这是一个例子:
use strict;
use warnings;
use Tie::File;
my ($mapping, $input) = @ARGV;
tie my @input, 'Tie::File', $input or die "Unable to open '$input': $!";
for my $line (@input) {
open my $mapfh, '<', $mapping or die "Unable to open '$mapping': $!";
while (<$mapfh>) {
chomp;
my ($key, $value) = split /=/;
$line =~ s/\Q$key/$value/g;
}
}
最后,为每行输入保持打开和读取映射文件是非常低效的,最好从其内容构建一个正则表达式并在整个程序中使用它。此版本首先从映射文件构建哈希%mapping
,然后通过将quotemeta
应用于每个哈希键以转义任何正则表达式元字符,然后使用正则表达式交替运算符{{1}连接它们来创建正则表达式}。密钥按降序排序,以便找到最长匹配并优先替换较短匹配。
|
答案 1 :(得分:0)
如果我可以将文件指针移动到行的开头,那么我可以用:
覆盖print INPUT $_,"\n"
您的前提是错误的:假设字节序列00 01 02
和规则01 = A1 A2
,结果字节序列将是00 A1 A2
而不是00 A1 A2 02
。解决方法包括:
Tie::File
模块。 seek
不是一个好主意:您将被限制为修复长度替换,seek
和tell
对字节而不是字符进行操作。如果你真的必须使用就地编辑,你可以使用这个循环:
my $beginning_of_line = tell $fh;
while (<$fh>) {
# do processing
seek $fh, $beginning_of_line, 0;
# do update
} continue {$beginning_of_line = tell $fh}
此外,您对输入文件进行了多次传递。假设令牌序列a b c
以及规则b = d e
和d = f
,您将生成序列a f e c
或a d e c
,具体取决于订单规则!这可能不是你想要的。
另外,请考虑规则a = c
和a b = d
之间在输入a b
上的模糊性。这会产生c b
还是d
?