2008-10-29 62 views
11

我一直在试图编写一个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编辑所有文件吗?

请注意,我不需要保留每个修改文件的副本;我有“时间都subversioned =)

更新:我想这对Cygwin

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

但它看起来像我的参数列表爆炸所允许的最大尺寸。事实上,我得到很奇怪的错误在Cygwin ...

+0

您应该注意到您正在运行Windows。 – 2008-10-30 04:19:33

回答

13

如果您使用*ARGV之前分配@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; 
} 

这应该是你想要的。

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

2

你可以使用find

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

这将列出所有的文件名递归,然后xargs将读取其标准输入和运行的剩余命令行结尾处附加了文件名。关于xargs的一个好处是,如果它构建的命令行太长而无法一次运行,它会多次运行命令行。

请注意,我不知道是否find完全明白选择文件的所有外壳的方法,因此,如果上述不工作,那么或许尝试:

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

当使用管道这样的,我喜欢建立命令行并在继续之前单独运行每个部分,以确保每个程序都能获得所需的输入。所以你可以首先运行零件而不用xargs来检查它。

我刚想到,虽然你没有这么说,但你可能在Windows上,因为你正在寻找文件后缀。在这种情况下,上述管道可以使用Cygwin运行。开始时可以编写一个Perl脚本来完成同样的事情,但是您必须自己进行就地编辑,因为在这种情况下您无法利用-i开关。

+0

试过 找。 -name'*。{cs,aspx,ascx}' 没有运气,但grep版本列出了这些文件。太好了! 但是当我运行的所有命令我得到这个: xargs的过程:perl:参数列表过长 – Seiti 2008-10-29 23:01:04

+0

xargs的也可以限制每个命令行上传递的参数数目,如果不能确定命令行的最大长度。 xargs使用-L或-n选项,具体取决于它是哪个版本(请参见手册页)。 – 2008-10-29 23:03:02

+0

如果您要使用find&xargs,请使用-print0和-0来避免使用空格的文件名问题。 find -print0 ... | xargs -0 ... – Schwern 2008-10-30 00:14:22

4

变化

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术语)。

的格格是不是确有必要,我只是被那些不chomp编一个太多次咬伤行(即使你删除掉chomp,改变print $out "$line\n";print $out $line;)。

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


注意,我本来在代码中的if语句。这很可能是错误的。那只会复制到与“thisgoesout”正则表达式匹配的行(当然用“thisgoesin”取代它),同时默默地吞噬其余行。

7

您可能感兴趣的File::Transaction::AtomicFile::Transaction

f的概要:: T ::一个看起来与你正在试图做的非常相似:

# 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 ([email protected]) { 
     $ft->revert; 
     die "update aborted: [email protected]"; 
    } 
    else { 
     $ft->commit; 
    } 

夫妇,与文件::找到你已经写好的,你应该很好去。

6

您可以使用Tie :: File来扩展访问大文件并将其更改。请参阅联机帮助页(man 3perl Tie :: File)。

+0

为什么要将他们指向人(3perl)而不是Perldoc? – ephemient 2008-10-29 23:47:10

1

感谢ephemient在这个问题上和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" }; 

现在,我甚至可以循环通过散列含正则表达式=>潜艇的作品!

相关问题