0

我有 2 个文件。

  1. 名为 input.txt 的混淆文件
  2. 第二个名为 mapping.txt 的文件由键值对组成。

我想从 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;

代码简要概述:

  1. 以读取模式打开 mapping.txt 文件。
  2. 由于每一行都是一个键值对,因此将其拆分为键和值。
  3. 以覆盖模式打开 input.txt 文件。
  4. 检查是否在当前行中找到密钥。
  5. 如果找到键,则用忽略键中任何元字符的值替换键(通过前缀 \Q)
  6. 此时,文件指针将位于行尾,因为前面的语句将扫描整行以查找密钥并替换它。
  7. 如果我可以将文件指针移动到行首,那么我可以覆盖:

    打印输入 $_,"\n"

  8. 我尝试查找 seek 功能,但无法找到为此目的使用它的方法。

完成后,代码将关闭文件。它将从 mapping.txt 中选择下一个键值对,然后再次扫描输入文件,从开始查找匹配项并替换它们。

最重要的一点是,每次内部 while 循环都会在 input.txt 上运行,该 input.txt 是在内部 while 循环的上一次迭代中修改的。这样,任何成功的查找和替换操作都会继续保存在 input.txt 文件中。

我该怎么做呢?

谢谢。

4

2 回答 2

3

首先,您应该使用词法文件句柄,即 的三参数形式open,并始终检查状态以确保 anopen已成功(就像您对映射文件而不是输入文件所做的那样)。

您建议的解决方案(在使用前倒退到行首)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命令行选项,它强制文件自动重命名,并在需要时保存备份。使用bare-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到每个散列键以转义任何正则表达式元字符来创建一个正则表达式,然后将它们与正则表达式交替运算符连接起来|。键按长度降序排序,以便找到最长的匹配并将其替换为优先于较短的匹配。

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-08T10:24:34.757 回答
0

如果我可以将文件指针移动到行首,那么我可以覆盖:

print INPUT $_,"\n"

您的前提是错误的:假设字节序列00 01 02和规则01 = A1 A2,生成的字节序列将是00 A1 A2而不是00 A1 A2 02。解决方法包括:

  • 使用Tie::File模块。
  • 一旦您的通行证完成,写入另一个文件,并将第二个文件重命名为原始文件。这可能是最有效和可扩展的。

seeking 不是一个好主意:您将被限制为固定长度的替换,并且seektell字节而不是字符进行操作。如果你真的必须使用就地编辑,你可以使用这个循环:

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 ed = f,您将产生序列a f e ca d e c 取决于规则的顺序!这可能不是你想要的。此外,请考虑规则和输入
之间的歧义。这会产生或吗?a = ca b = da bc bd

于 2012-10-08T10:34:16.283 回答