有没有一种简单的方法来进行批量文件文本替换?

Posted

技术标签:

【中文标题】有没有一种简单的方法来进行批量文件文本替换?【英文标题】:Is there a simple way to do bulk file text substitution in place? 【发布时间】:2010-09-19 21:43:35 【问题描述】:

我一直在尝试编写一个 Perl 脚本来替换我项目的所有源文件中的一些文本。我需要类似的东西:

perl -p -i.bak -e "s/thisgoesout/thisgoesin/gi" *.cs,aspx,ascx

但这会解析所有目录的文件递归

我刚刚开始了一个脚本:

use File::Find::Rule;
use strict;

my @files = (File::Find::Rule->file()->name('*.cs','*.aspx','*.ascx')->in('.'));

foreach my $f (@files)
    if ($f =~ s/thisgoesout/thisgoesin/gi) 
           # In-place file editing, or something like that
    

但现在我被困住了。有没有使用 Perl 编辑所有文件的简单方法?

请注意,我不需要为每个修改过的文件保留一份副本;我把他们都颠覆了 =)

更新:我在Cygwin 上试过这个,

perl -p -i.bak -e "s/thisgoesout/thisgoesin/gi" *,*/*,*/*/*.cs,aspx,ascx

但看起来我的参数列表已爆炸到允许的最大大小。事实上,我在 Cygwin 上遇到了非常奇怪的错误......

【问题讨论】:

您可能应该注意到您正在运行 Windows。 【参考方案1】:

你可以使用find:

find . -name '*.cs,aspx,ascx' | xargs perl -p -i.bak -e "s/thisgoesout/thisgoesin/gi"

这将递归地列出所有文件名,然后xargs 将读取其标准输入并运行命令行的其余部分,并在末尾附加文件名。 xargs 的一个好处是,如果它构建的命令行太长而无法一次性运行,它将多次运行命令行。

请注意,我不确定find是否完全理解选择文件的所有shell方法,所以如果上述方法不起作用,那么不妨试试:

find . | grep -E '(cs|aspx|ascx)$' | xargs ...

当使用这样的管道时,我喜欢在继续之前构建命令行并单独运行每个部分,以确保每个程序都能获得所需的输入。所以你可以在没有xargs的情况下运行该部分来检查它。

我突然想到,虽然您没有这么说,但由于您要查找的文件后缀,您可能在 Windows 上。在这种情况下,可以使用 Cygwin 运行上述管道。可以编写一个 Perl 脚本来做同样的事情,就像你开始做的那样,但你必须自己进行就地编辑,因为在那种情况下你不能利用 -i 开关。

【讨论】:

试过 find 。 -name '*.cs,aspx,ascx' 没有运气,但 grep 版本列出了文件。好的!但是当我运行所有命令时,我得到这个: xargs: perl: Argument list too long xargs 也可以限制在每个命令行上传递的参数数量,如果它不能确定命令行的最大长度。根据 xargs 的版本使用 -L 或 -n 选项(参见手册页)。 如果您要使用 find & xargs,请使用 -print0 和 -0 以避免文件名带有空格的问题。找到-print0 ... | xargs -0 ...【参考方案2】:

改变

foreach my $f (@files)
    if ($f =~ s/thisgoesout/thisgoesin/gi) 
           #inplace file editing, or something like that
    

foreach my $f (@files)
    open my $in, '<', $f;
    open my $out, '>', "$f.out";
    while (my $line = <$in>)
        chomp $line;
        $line =~ s/thisgoesout/thisgoesin/gi
        print $out "$line\n";
    

这假设模式不跨越多行。如果模式可能跨行,您需要在文件内容中啜饮。 (“slurp”是一个非常常见的 Perl 术语)。

实际上没有必要,我只是被不是chomped 的行咬了太多次(如果你放弃chomp,请将print $out "$line\n"; 更改为print $out $line;)。

同样,您可以将 open my $out, '&gt;', "$f.out"; 更改为 open my $out, '&gt;', undef; 以打开一个临时文件,然后在替换完成后将该文件复制回原始文件。事实上,特别是如果你在整个文件中啜饮,你可以简单地在内存中进行替换,然后覆盖原始文件。但是我犯了足够多的错误,我总是写入一个新文件并验证内容。


注意,我最初在该代码中有一个 if 语句。那很可能是错误的。那只会复制与正则表达式“thisgoesout”匹配的行(当然用“thisgoesin”替换),同时默默地吞噬其余部分。

【讨论】:

【参考方案3】:

您可能对File::Transaction::Atomic 或File::Transaction 感兴趣

F::T::A 的 SYNOPSIS 看起来与您尝试执行的操作非常相似:

  # In this example, we wish to replace 
  # the word 'foo' with the word 'bar' in several files, 
  # with no risk of ending up with the replacement done 
  # in some files but not in others.

  use File::Transaction::Atomic;

  my $ft = File::Transaction::Atomic->new;

  eval 
      foreach my $file (@list_of_file_names) 
          $ft->linewise_rewrite($file, sub 
               s#\bfoo\b#bar#g;
          );
      
  ;

  if ($@) 
      $ft->revert;
      die "update aborted: $@";
  
  else 
      $ft->commit;
  

再加上 File::Find 你已经写好了,你应该可以开始了。

【讨论】:

【参考方案4】:

您可以使用 Tie::File 可扩展地访问大文件并就地更改它们。请参阅手册页(man 3perl Tie::File)。

【讨论】:

是的,Tie::File 就是为这种事情而创建的。【参考方案5】:

如果您在使用*ARGV(又名菱形&lt;&gt;)之前分配@ARGV,则$^I/-i 将对这些文件起作用,而不是在命令行中指定的文件。

use File::Find::Rule;
use strict;

@ARGV = (File::Find::Rule->file()->name('*.cs', '*.aspx', '*.ascx')->in('.'));
$^I = '.bak';  # or set `-i` in the #! line or on the command-line

while (<>) 
    s/thisgoesout/thisgoesin/gi;
    print;

这应该完全符合您的要求。

如果您的模式可以跨越多行,请在 &lt;&gt; 之前添加 undef $/;,以便 Perl 一次对整个文件进行操作,而不是逐行操作。

【讨论】:

【参考方案6】:

感谢 ehemient 关于这个问题和this answer,我得到了这个:

use File::Find::Rule;
use strict;

sub ReplaceText 
    my $regex = shift;
    my $replace = shift;

    @ARGV = (File::Find::Rule->file()->name('*.cs','*.aspx','*.ascx')->in('.'));
    $^I = '.bak';
    while (<>) 
        s/$regex/$replace->()/gie;
        print;
    


ReplaceText qr/some(crazy)regexp/, sub  "some $1 text" ;

现在我什至可以遍历包含 regexp=>subs 条目的哈希!

【讨论】:

您可能应该在此例程中 localize @ARGV$^I,因为这些变量具有相当全局的影响。

以上是关于有没有一种简单的方法来进行批量文件文本替换?的主要内容,如果未能解决你的问题,请参考以下文章

使用脚本批量替换文本内容

使用re.sub将文本替换为匹配到的内容

在Vi里面实现字符串的批量替换

有没有一种简单的方法可以通过名称(来自文本文件)来处理应用程序的变量?

如何用批处理替换文本内容?

如何用Python来进行查询和替换一个文本字符串