2012-10-08 63 views
0

我有2个文件。名为input.txt Perl - 将指针移动到行首

    1. 模糊处理文件调用的mapping.txt由关键值对的第二文件。

    我想从input.txt中的mapping.txt中找到每个出现的密钥,并将其替换为与该密钥对应的值。

    请注意,我想在每次成功匹配时覆盖input.txt中行的内容。

    我写了下面的代码:

    #! /usr/bin/perl 
    
    use strict; 
    use warnings; 
    
    (my $mapping,my $input)[email protected]; 
    
    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. 我试图寻找了搜索功能但是无法想出一个方法来将其用于此目的。

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

    最重要的一点是,每次内部while循环都将在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应用于每个散列键来转义任何正则表达式元字符,然后使用正则表达式替换运算符|将它们加入,从而创建正则表达式。按键按降序排列,以便找到最长的匹配项,并优先于较短的项目进行替换。

    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; 
    } 
    
    +0

    这太美了。非常感谢:)你解释得很好,现在我会试验:) –

    0

    如果我能移动文件指针行的开始,那么我可以覆盖有:

    print INPUT $_,"\n" 
    

    你的前提是错误的:假设字节序列00 01 02和规则01 = A1 A2,结果字节序列将是00 A1 A2而不是00 A1 A2 02。方法包括:

    • 使用Tie::File模块。
    • 写入其他文件,并在通过完成后将第二个文件重命名为原始文件。这可能是最高效和可扩展的。

    seek ING是不是一个好主意:你会被限制在固定长度的置换和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 b上的规则a = ca b = d之间的不明确性。这是否产生c bd

    +0

    从['Tie :: File'文档](https://metacpan.org/module/Tie%3a%3aFile#DESCRIPTION):* The文件没有加载到内存中,所以即使对于巨大的文件这也可以工作* – Borodin