我有两个文件。
我希望在mapping.txt中查找input.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;
守则简介:
完成后,代码将关闭该文件。它将从mapping.txt中选择下一个键值对,并从一开始就再次扫描输入文件,寻找匹配项并替换它们。
最重要的一点是,每次内部while循环都将在input.txt上操作,这是在上一次内while循环的迭代中修改的。这样,任何成功的查找和替换操作都将继续保存在input.txt文件中。
我该怎么做?
谢谢。
发布于 2012-10-08 02:24:34
首先,您应该使用词法文件句柄( open
的三个参数形式),并且始终检查状态以确保open
成功(就像您对映射文件而不是输入文件所做的那样)。
建议的解决方案在使用print
之前返回到行的开始将无法工作,因为您无法更新文件的一部分,除非您的替换数据与它要替换的数据大小完全相同。在你的情况下,这通常是不正确的。
有许多解决方案,第一个也是最简单的方法是反转循环,并将映射文件的read循环放置在输入文件的read循环中。您的代码将如下所示:
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
应用于每个散列键来转义任何regex元字符,然后将其与regex交替运算符|
连接起来,从而创建regex。键按降序长度排序,以便找到最长的匹配项,并优先替换较短的匹配项。
use strict;
use warnings;
use Tie::File;
my ($mapping, $input) = @ARGV;
open my $mapfh, '<', $mapping or die "Unable to open '$mapping': $!";
my %mapping = map { chomp; /\S/ ? split /=/ : () } <$mapfh>;
my $regex = join '|', map quotemeta, sort { length $b <=> length $b } keys %mapping;
tie my @input, 'Tie::File', $input or die "Unable to open '$input': $!";
for my $line (@input) {
$line =~ s/($regex)/$mapping{$1}/g;
}
发布于 2012-10-08 02:34:16
如果我可以将文件指针移动到行的开头,那么我可以用: 打印输入$_,"\n“
您的前提是错误的:假设字节序列00 01 02
和规则01 = A1 A2
,产生的字节序列将是00 A1 A2
而不是00 A1 A2 02
。解决这一问题的方法包括:
Tie::File
模块。seek
ing不是一个好主意:您将被限制为固定长度的替换,而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
的a b = d
之间的模糊性。这会产生c b
还是d
?
https://stackoverflow.com/questions/12779186
复制相似问题