Perl - 将指针移动到行首

时间:2012-10-08 09:48:12

标签: perl seek

我有2个文件。

  1. 名为input.txt的混淆文件
  2. 第二个名为mapping.txt的文件,由键值对组成。
  3. 我想在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. 如果我可以将文件指针移动到行的开头,那么我可以用以下内容覆盖:

      打印INPUT $ _,“\ n”

    8. 我尝试查找搜索功能,但无法找到将其用于此目的的方法。
    9. 完成后,代码将关闭文件。它将从mapping.txt中选择下一个键值对,并再次扫描输入文件,从开始查找匹配并替换它们。

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

      我该怎么做?

      感谢。

2 个答案:

答案 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不是一个好主意:您将被限制为修复长度替换,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 = d之间在输入a b上的模糊性。这会产生c b还是d