【问题标题】:Need to open a file and replace multiple strings需要打开一个文件并替换多个字符串
【发布时间】:2019-10-01 13:43:44
【问题描述】:

我有一个非常大的 xml 文件。它里面有一定的递增数字,我想用不同的递增数字代替。我已经看过了,这是以前有人在这里建议的。不幸的是我不能让它工作:(

在下面的代码中,所有 40960 实例都应替换为 41984,所有 40961 实例都应替换为 41985 等等。没有任何反应。我做错了什么?

use strict;
use warnings;

my $old = 40960;
my $new = 41984;
my $string;

my $file = 'file.txt';

rename($file, $file.'.bak');
open(IN, '<'.$file.'.bak') or die $!;
open(OUT, '>'.$file) or die $!;

$old++;
$new++;

for (my $i = 0; $i < 42; $i++) {
    while(<IN>) {
        $_ =~ s/$old/$new/g;
        print OUT $_;
    }
}

close(IN);
close(OUT);

【问题讨论】:

  • 对于for 循环中的每次迭代,您可能必须回到输入文件的开头,请参阅perldoc seek。但是可能有更有效的方法可以让您一次替换所有数字

标签: string file perl replace


【解决方案1】:

这是另一种方法,它将输入文件读入一个字符串并一次完成所有替换:

use strict;
use warnings;

{
my $old = 40960;
my $new = 41984;

my ($regexp) = map { qr/$_/ } join '|', map { $old + $_ } 0..41;

my $file = 'file.txt';
rename($file, $file.'.bak');
open(IN, '<'.$file.'.bak') or die $!;
my $str = do {local $/; <IN>};
close IN;
$str =~ s/($regexp)/do_subst($1, $old, $new)/ge;

open(OUT, '>'.$file) or die $!;
print OUT $str;
close OUT;

}

sub do_subst {
    my ( $old, $old_base, $new_base ) = @_;
    my $i = $old - $old_base;
    my $new = $new_base + $i;
    return $new;
}

注意:使用Regexp::Assemble 可能会提高效率

【讨论】:

  • 我的xml文件很大,不知道转换成字符串会不会出问题。
【解决方案2】:

这是一个逐行运行的示例,因此文件的大小无关紧要。该示例假设您要替换“45678”之类的内容,而不是“fred45678”。该示例还假设存在一个数字范围,并且您希望将它们替换为由一个常数偏移的新范围。

#!/usr/bin/perl

use strict;
use warnings;

use constant MIN => 40000;
use constant MAX => 90000;
use constant DIFF => +1024;

sub repl { $_[0] >= MIN && $_[0] <= MAX ? $_[0] + DIFF : $_[0] }

while (<>) {
    s/\b(\d+)\b/repl($1)/eg;
    print;
}
exit(0);

使用要转换的文件作为参数调用,它会在标准输出上产生更改的输出。使用以下输入...

foo bar 123
40000 50000 60000 99999
fred60000
fred 60000 fred

...它产生这个输出。

foo bar 123
41024 51024 61024 99999
fred60000
fred 61024 fred

这里有几个经典的 Perlisms,但如果你的 RTFM 适当,这个例子应该不难理解。

【讨论】:

  • 这看起来很有趣,但对于像我这样的初学者来说也相当复杂。您能否添加一些 cmets 来说明什么是什么?
  • repl() 是一个简单的函数,给定 x,如果 x >= MIN 且 x
【解决方案3】:

其他答案可为您的问题提供更好的解决方案。我的重点是解释为什么您的代码不起作用。

你的代码核心在这里:

$old++;
$new++;

for (my $i = 0; $i < 42; $i++) {
    while(<IN>) {
        $_ =~ s/$old/$new/g;
        print OUT $_;
    }
}

您在循环之外增加 $old$new 的值。而且您再也不会更改这些值。因此,您只进行了 42 次相同的替换(将 40961 更改为 41985)。您永远不会尝试更改任何其他数字。

另外,查看从IN 读取的while 循环。在您的第一次迭代中(当$i 为0 时)您从IN 读取所有数据,并且文件指针留在文件末尾。因此,当您在第二次迭代(以及所有后续迭代)中再次进入 while 循环时,您根本不会从文件中读取任何数据。您需要在每次迭代结束时将文件指针重置为文件的开头。

哦,基本逻辑是错误的。如果您考虑一下,您最终会将每一行写入输出文件 42 次。在编写该行之前,您需要进行所有可能的替换。所以你的内循环必须是外循环(反之亦然)。

把这些建议放在一起,你需要这样的东西:

my $old    = 40960;
my $change = 1024;

while (<IN>) {
    # Easier way to write your loop
    for my $i ( 1 .. 42 ) {
        my $new = $old + $change;
        # Use \b to mark word boundaries
        s/\b$old\b/$new/g;
        $old++;
    }
    # Print each output line only once
    print OUT $_;
}

【讨论】:

  • 谢谢,我意识到自己在循环放置方面失败了,但完全错过了文件句柄问题。
猜你喜欢
  • 1970-01-01
  • 2016-09-09
  • 1970-01-01
  • 2013-12-27
  • 2019-01-07
  • 2019-04-05
  • 2020-11-08
  • 2017-03-26
相关资源
最近更新 更多