【问题标题】:Replace multiple lines in text file替换文本文件中的多行
【发布时间】:2017-12-20 17:22:49
【问题描述】:

我有包含以下文本(以及其他文本)的文本文件

DIFF_COEFF= 1.000e+07,1.000e+07,1.000e+07,1.000e+07,
1.000e+07,1.000e+07,1.000e+07,1.000e+07,1.000e+07,1.000e+07,1.000e+07,
1.000e+07,1.000e+07,1.000e+07,1.000e+07,1.000e+07,1.000e+07,1.000e+07,
1.000e+07,1.000e+07,1.000e+07,1.000e+07,1.000e+07,1.000e+07,1.000e+07,
1.000e+07,1.000e+07,1.000e+07,1.000e+07,1.000e+07,1.000e+07,1.000e+07,
1.000e+07,1.000e+07,1.000e+07,1.000e+07,1.000e+07,4.000e+05,

我需要用以下文本替换它:

DIFF_COEFF= 2.000e+07,2.000e+07,2.000e+07,2.000e+07,
2.000e+07,2.000e+07,2.000e+07,2.000e+07,2.000e+07,2.000e+07,2.000e+07,
2.000e+07,2.000e+07,2.000e+07,2.000e+07,2.000e+07,2.000e+07,2.000e+07,
2.000e+07,2.000e+07,2.000e+07,2.000e+07,2.000e+07,2.000e+07,2.000e+07,
2.000e+07,2.000e+07,2.000e+07,2.000e+07,2.000e+07,2.000e+07,2.000e+07,
2.000e+07,2.000e+07,2.000e+07,2.000e+07,2.000e+07,8.000e+05,

上面的每一行对应文本文件中的一个新行。

经过一番谷歌搜索,我认为在下面使用 Perl 可能有效,但它没有。我收到错误消息

在-e line 1, chunk 1 处非法除以零

s_orig='DIFF_COEFF=*4.000e+05,'

s_new='DIFF_COEFF= 2.000e+07,2.000e+07,2.000e+07,2.000e+07,\n2.000e+07,2.000e+07,2.000e+07,2.000e+07,2.000e+07,2.000e+07,2.000e+07,\n2.000e+07,2.000e+07,2.000e+07,2.000e+07,2.000e+07,2.000e+07,2.000e+07,\n2.000e+07,2.000e+07,2.000e+07,2.000e+07,2.000e+07,2.000e+07,2.000e+07,\n2.000e+07,2.000e+07,2.000e+07,2.000e+07,2.000e+07,2.000e+07,2.000e+07,\n2.000e+07,2.000e+07,2.000e+07,2.000e+07,2.000e+07,8.000e+05,'

perl -0 -i -pe "s:\Q${s_orig}\E:${s_new}:/igs" file.txt

这里有人知道正确的方法吗?

编辑 - 更多细节:此块之后的文本是“DIFF_COEFF_Q=”,后跟相同的一组数字,所以我需要搜索并替换显示的特定行。文本文件不是很大。

【问题讨论】:

  • (1) “text below”(DIFF_COEFF 及以下5行)与“other text”有何区别?例如,最后一行数字后面是什么? (2) 您需要像示例中那样将每个数字乘以 2,还是直接用文本替换?
  • 值列表真的以逗号结尾吗?
  • 在替换结束时删除/,即s:…:…:igs,而不是s:…:…:/igs
  • 所以你不想做计算,你只想从字面上替换?
  • 鲍罗丁 - 为什么不情愿?我已经按照我的理解回答了每个人的问题。我想你不希望我把整个文本文件粘贴到这里,所以我说了我认为重要的内容。 “不幸的是,DIFF_COEFF 行下方的文本与显示的行非常相似(不同之处仅在于它以 DIFF_COEFF_Q 开头)”就我所见,下面的文本是明确的。这足以让 zdim 提供解决方案。长字符串替换工作完美。我提到的文字片段不再重复,如果这是你的意思(不清楚)。

标签: perl text replace


【解决方案1】:

将文件复制到一个新文件,但在这些标记之间的文本范围内删除替换文本。然后移动该文件以替换原始文件,因为根据问题中尝试的perl -0 -i 判断可能需要它。

请注意,在更改文件时,我们必须构建新内容,然后替换文件。有几种方法可以做到这一点,并且有一些模块可以使其更容易,如下所示。

下面的代码使用range operator 并且它返回范围内行的计数器,1 用于第一个,以E0 结尾的数字用于最后一个。因此,当我们在最后一行写入替换文本(和后区域结束标记)时,我们不会复制该区域内的行。

根据问题编辑,我认为感兴趣的区域在 DIFF_COEFF_Q= 行之前结束。

use warnings;
use strict;
use feature 'say';
use File::Copy 'move';

my $replacement = "replacement text";

my $file     = 'input.txt';
my $out_file = 'new_' . $file;

open my $fh_out, '>', $out_file or die "Can't open $out_file: $!";
open my $fh,     '<', $file     or die "Can't open $file: $!";

while (<$fh>) 
{
    if (my $range_cnt = /^\s*DIFF_COEFF\s*=/ .. /^\s*DIFF_COEFF_Q\s*=/) #/
    {
        if ($range_cnt =~ /E0$/)
        {
            print $fh_out $replacement;  # may need a newline
            print $fh_out $_;         
        }
    }   
    else { 
        print $fh_out $_; 
    }
}
close $fh     or die "Can't close $file: $!";      # don't overwrite original
close $fh_out or die "Can't close $out_file: $!";  # if there are problems

#move $out_file, $file or die "Can't move $file to $out_file: $!";

如果您想替换原始文件,请在对您的实际文件进行足够好的测试后取消注释 move 行。 $replacement 之后可能需要也可能不需要换行符,具体取决于它。

另一种方法是使用标志进入/离开该范围。但这不会更干净,因为有两个不同的操作,进入范围时停止复制,离开时写入替换。因此需要设置和检查多个标志,最终可能会更混乱。

如果文件永远不会很大,那么在内存中读取和处理文件会更简单。然后打开同一个文件进行写入并转储新内容

my $text = do {  # slurp file into a scalar
    local $/; 
    open my $fh, '<', $file or die "Can't open $file: $!"; 
    <$fh> 
};

$text =~ s/^\s*DIFF_COEFF\s*=.*?(\n\s*DIFF_COEFF_Q)/$replacement$1/ms;

# Change $out_file to $file to overwrite
open my $fh_out, '>', $out_file or die "Can't open $out_file: $!";
print $fh_out $text;

这里/m 修饰符用于多行模式,我们可以使用^ 作为行的开头(不是整个字符串),这里有什么帮助。 /s 也使 . 匹配换行符。另请注意,我们可以使用Path::Tiny slurp 文件,就像:my $text = path($file)-&gt;slurp;

另一种选择是使用Path::Tiny,在较新的版本中有editedit_lines 方法

use Path::Tiny;
                      # NOTE: edits $file in place (changes it)
path($file)->edit( 
    sub { s/DIFF_COEFF=.*?(\n\s*DIFF_COEFF_Q)/$replacement$1/s } 
);

如需了解更多信息,请参阅 this postthis postthis post

第一种和最后一种方式改变文件的inode号。如果有问题,请参阅this post

【讨论】:

  • @PeterW 为提到的其他方式添加了代码。
【解决方案2】:

这是你犯的一个有趣的错误,我知道是什么导致你犯了这个错误。但我认为我从未见过其他人犯过同样的错误:-)

你的替换语句是这样的:

s:\Q${s_orig}\E:${s_new}:/igs

所以您决定使用: 作为替换运算符的分隔符。但是您想使用选项igs,并且在您看到人们谈论替换运算符的选项的任何地方,他们都在谈论使用/ 来介绍选项。因此,您已将 /igs 添加到替换运算符中。

但是你错过了(我完全理解为什么)是选项之前的/ 实际上是标准的结束分隔符s/.../.../,版本的替换运算符。如果您更改分隔符(如您所做的那样),那么您只需要更改的结束分隔符。

在您的情况下,Perl 不期望 / 因为它已经看到了结束分隔符。因此,它决定/ 是一个除法运算符,并尝试将您的替换结果除以igs。它将igs 解释为零,你就会得到错误。

解决方法是删除 / 所以:

s:\Q${s_orig}\E:${s_new}:/igs

变成:

s:\Q${s_orig}\E:${s_new}:igs

【讨论】:

  • 谢谢戴夫。你对我的愚蠢错误是正确的。但是,如果我删除最后的“/”,结果是我现在没有收到错误消息,但文件没有任何更改。
  • @PeterW:我想我们已经回答了你的问题。我建议您在此处接受答案并发布一个新问题,其中包含有关您正在尝试做什么的更多详细信息。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-03-19
  • 2017-06-06
  • 2018-07-18
相关资源
最近更新 更多