【问题标题】: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。

标签: perl search command-line replace bulk


【解决方案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::AtomicFile::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,因为这些变量具有相当全局的影响。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-06-06
        • 2011-01-26
        • 1970-01-01
        • 2022-11-25
        • 1970-01-01
        • 2021-10-16
        相关资源
        最近更新 更多