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

发布于 2024-07-08 14:56:20 字数 907 浏览 9 评论 0原文

我一直在尝试编写一个 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 上遇到了非常奇怪的错误......

I've been trying to code a Perl script to substitute some text on all source files of my project. I'm in need of something like:

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

But that parses all the files of a directory recursively.

I just started a script:

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
    }
}

But now I'm stuck. Is there a simple way to edit all files in place using Perl?

Please note that I don't need to keep a copy of every modified file; I'm have 'em all subversioned =)

Update: I tried this on Cygwin,

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

But it looks like my arguments list exploded to the maximum size allowed. In fact, I'm getting very strange errors on Cygwin...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

相守太难 2024-07-15 14:56:21

如果您在使用 *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 一次操作整个文件而不是行 -逐行。

If you assign @ARGV before using *ARGV (aka the diamond <>), $^I/-i will work on those files instead of what was specified on the command line.

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;
}

This should do exactly what you want.

If your pattern can span multiple lines, add in a undef $/; before the <> so that Perl operates on a whole file at a time instead of line-by-line.

眼波传意 2024-07-15 14:56:21

您可能对 File::Transaction::Atomic 或 < a href="http://search.cpan.org/perldoc?File::Transaction" rel="nofollow noreferrer">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 结合起来,你应该可以开始了。

You may be interested in File::Transaction::Atomic or File::Transaction

The SYNOPSIS for F::T::A looks very similar with what you're trying to do:

  # 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;
  }

Couple that with the File::Find you've already written, and you should be good to go.

擦肩而过的背影 2024-07-15 14:56:21

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

You can use Tie::File to scalably access large files and change them in place. See the manpage (man 3perl Tie::File).

贱人配狗天长地久 2024-07-15 14:56:21

更改

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,改变 chomp) >print $out "$line\n"; 到 print $out $line;)。

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


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

Change

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

To

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";
    }
}

This assumes that the pattern doesn't span multiple lines. If the pattern might span lines, you'll need to slurp in the file contents. ("slurp" is a pretty common Perl term).

The chomp isn't actually necessary, I've just been bitten by lines that weren't chomped one too many times (if you drop the chomp, change print $out "$line\n"; to print $out $line;).

Likewise, you can change open my $out, '>', "$f.out"; to open my $out, '>', undef; to open a temporary file and then copy that file back over the original when the substitution's done. In fact, and especially if you slurp in the whole file, you can simply make the substitution in memory and then write over the original file. But I've made enough mistakes doing that that I always write to a new file, and verify the contents.


Note, I originally had an if statement in that code. That was most likely wrong. That would have only copied over lines that matched the regular expression "thisgoesout" (replacing it with "thisgoesin" of course) while silently gobbling up the rest.

像极了他 2024-07-15 14:56:21

您可以使用 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 的部分来检查它。

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

| xargs ...

当使用这样的管道时,我喜欢构建命令行并在继续之前单独运行每个部分,以确保每个程序都获得它想要的输入。 因此,您可以先运行没有 xargs 的部分来检查它。

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

You could use find:

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

This will list all the filenames recursively, then xargs will read its stdin and run the remainder of the command line with the filenames appended on the end. One nice thing about xargs is it will run the command line more than once if the command line it builds gets too long to run in one go.

Note that I'm not sure whether find completely understands all the shell methods of selecting files, so if the above doesn't work then perhaps try:

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

When using pipelines like this, I like to build up the command line and run each part individually before proceeding, to make sure each program is getting the input it wants. So you could run the part without xargs first to check it.

It just occurred to me that although you didn't say so, you're probably on Windows due to the file suffixes you're looking for. In that case, the above pipeline could be run using Cygwin. It's possible to write a Perl script to do the same thing, as you started to do, but you'll have to do the in-place editing yourself because you can't take advantage of the -i switch in that situation.

| xargs ...

When using pipelines like this, I like to build up the command line and run each part individually before proceeding, to make sure each program is getting the input it wants. So you could run the part without xargs first to check it.

It just occurred to me that although you didn't say so, you're probably on Windows due to the file suffixes you're looking for. In that case, the above pipeline could be run using Cygwin. It's possible to write a Perl script to do the same thing, as you started to do, but you'll have to do the in-place editing yourself because you can't take advantage of the -i switch in that situation.

哽咽笑 2024-07-15 14:56:21

感谢关于这个问题和这个答案的ephemient,我得到了这个:

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 条目的哈希!

Thanks to ephemient on this question and on this answer, I got this:

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" };

Now I can even loop through a hash containing regexp=>subs entries!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文