我一直在尝试编写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上遇到了非常奇怪的错误......
答案 0 :(得分: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一次对整个文件进行操作,而不是逐行操作。
答案 1 :(得分:7)
您可能对File::Transaction::Atomic或File::Transaction
感兴趣F :: T :: A的概要与您尝试的内容非常相似:
# 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你已经写好了,你应该好好去。
答案 2 :(得分:6)
您可以使用Tie :: File来可伸缩地访问大文件并在适当的位置更改它们。请参阅联机帮助页(man 3perl Tie :: File)。
答案 3 :(得分: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
次多次排除(如果你放弃chomp
,更改print $out "$line\n";
到print $out $line;
)。
同样,您可以将open my $out, '>', "$f.out";
更改为open my $out, '>', undef;
以打开临时文件,然后在替换完成后将该文件复制回原始文件。事实上,特别是如果你在整个文件中啜食,你可以简单地在内存中进行替换,然后在原始文件上进行写入。但是我犯了足够的错误,我总是写一个新文件,并验证内容。
注意,我最初在该代码中有一个if语句。这很可能是错的。这只会复制与正则表达式“thisgoesout”匹配的行(当然用“thisgoesin”替换它),同时默默地吞噬其余部分。
答案 4 :(得分:2)
您可以使用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
开关
答案 5 :(得分:1)
感谢这个问题和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 =&gt; subs条目的哈希值!