【问题标题】:Find/Replace in files recursively but touch only files with matches递归查找/替换文件,但只触摸匹配的文件
【发布时间】:2014-05-22 15:19:57
【问题描述】:

我想以递归方式快速搜索和替换有或没有 regexp 的文件。此外,我只需要在特定文件中进行搜索,并且我不想触摸与我的 search_pattern 不匹配的文件,否则 git 会认为所有已解析的文件都已被修改(它会发生 find . --exec sed)。

我尝试了许多我在互联网上使用 find、grep、sedack 找到的解决方案,但我不认为它们真的适合仅匹配特定文件。

最后我写了这个 perl 脚本:

#!/bin/perl
use strict;
use warnings;
use File::Find;

my $search_pattern  = $ARGV[0];
my $replace_pattern = $ARGV[1];
my $file_pattern    = $ARGV[2];
my $do_replace = 0;

sub process {
    return unless -f;
    return unless /(.+)[.](c|h|inc|asm|mac|def|ldf|rst)$/;
    open F, $_ or print "couldn't open $_\n" && return;

    my $file = $_;
    my $i = 0;

    while (<F>) {
        if (m/($search_pattern)/o) {$i++};
    }
    close F;

    if ($do_replace and $i)
    {
        printf "found $i occurence(s) of $search_pattern in $file\n";
        open F, "+>".$file or print "couldn't open $file\n" && return;
        while (<F>)
        {
            s/($search_pattern)/($replace_pattern)/g;
            print F;
        }
        close F;
    }
}

find(\&process, ".");   

我的问题是:

下面有没有更好的解决方案(不存在)?

`repaint -n/(.+)[.](c|h|inc|asm|mac|def|ldf|rst)$/ s/search/replacement/g .`

附属问题:

  1. 我的 perl 脚本怎么样?还不算太糟糕?我真的需要重新打开与我的 search_pattern 匹配的所有文件吗?

  2. 人们如何处理这项琐碎的任务?几乎每个优秀的文本编辑器都有“在文件中搜索和替换”功能,但 vim 没有。 vim 用户如何做到这一点?

编辑:

我也用ff | xargs perl -pi -e 's/foo/bar/g' 尝试了这个脚本 ff.pl,但它没有按我的预期工作。它创建了一个备份 .bak,即使我在 -pi 之后没有提供任何内容。这似乎是 cygwin 中的正常行为,但我不能真正使用 perl -pi -e

#!/bin/perl
use strict;
use warnings;
use File::Find;
use File::Basename;
my $ext = $ARGV[0];

sub process {
    return unless -f;
    return unless /\.(c|h|inc|asm|mac|def|ldf|rst)$/;
    print $File::Find::name."\n" ;
}

find(\&process, "."); 

重新编辑:

我终于遇到了这个解决方案(在cygwin下我需要删除备份文件)

find . | egrep '\.(c|h|asm|inc)$' | xargs perl -pi.winsucks -e 's/<search>/<replace>/g'
find . | egrep '\.(c|h|asm|inc)\.winsucks$' | xargs rm   

【问题讨论】:

  • 每个问题一个问题。

标签: regex perl search vim replace


【解决方案1】:

以下是您的代码的清理版本。

  • 始终在每个 perl 脚本的顶部包含 use strict;use warnings。如果您正在处理文件,请同时包含 use autodie;
  • 继续并吞下整个文件。这样,您只需读写一次即可。
  • 考虑在这种情况下使用File::Find::Rule。您使用File::Find 的实现有效,实际上可能是这种情况下的首选模块,但我喜欢后者的界面。
  • 我从正则表达式中删除了捕获组。 RHS 中的那些是错误,LHS 中的那些是多余的。

还有代码:

use strict;
use warnings;
use autodie;

use File::Find;

my $search_pattern  = $ARGV[0];
my $replace_pattern = $ARGV[1];
my $file_pattern    = $ARGV[2];

my $do_replace = 0;

sub process {
    return if !-f;
    return if !/[.](?:c|h|inc|asm|mac|def|ldf|rst)$/;

    my $data = do {
        open my $fh, '<', $_;
        local $/;
        <$fh>;
    };

    my $count = $data =~ s/$search_pattern/$replace_pattern/g
        or return;

    print "found $count occurence(s) of $search_pattern in $_\n";

    return if !$do_replace;

    open my $fh, '>', $_;
    print $fh $data;
    close $fh;
}

find(\&process, ".");   

【讨论】:

  • 哇!该脚本运行良好。它比 perl -pi -e 快得多(因为在 cygwin 上 perl 必须创建备份文件)
【解决方案2】:

还不错,但有几个小注意事项:

  • $do_replace 始终为 0,因此不会替换
  • in-place open F, "+>" 在 cygwin + windows 上不起作用
  • m/($search_pattern)/o /o 好,() 不需要。
  • $file_pattern 被忽略,你用你自己的覆盖它
  • s/($search_pattern)/($replace_pattern)/g;

    () 是不需要的,实际上会干扰 $replace_pattern 中的计数器

  • /(.+)[.](c|h|inc|asm|mac|def|ldf|rst)$/ 应该写成

    /\.(c|h|inc|asm|mac|def|ldf|rst)$/ 也可能是 /i

我真的需要重新打开与我的 search_pattern 匹配的每个文件吗?

  • 你不这样做。

对vim一窍不通,我用的是emacs,它有几种方法可以做到这一点。

【讨论】:

  • 谢谢!你说“+>”在cygwin + windows上不起作用。这可能是脚本损坏了我的文件的原因。似乎连while(&lt;F&gt;) {print F;} 都对我的文件做了坏事。
【解决方案3】:

下面的命令有什么问题?

:grep foo **/*.{foo,bar,baz}
:cw

它不会对任何 VCS 造成任何问题,并且是非常基本的 Vimming。

你说得对,Vim 没有提供专用的“文件中的搜索和替换”功能,但有plugins 可以做到这一点。

【讨论】:

  • 我有很多子目录,我不想输入所有可能的组合 **/*.{foo,bar} **/**/*.{foo,bar} ... 我想使用 grep -r foo *.{foo,bar,baz} 但它不起作用:(
  • 实际上grep enable **/*.{c,h,asm} 没有找到任何东西,而grep enable **/**/*.{c,h,asm} 工作但不是在所有子目录上。
【解决方案4】:

为什么不只是:

grep 'pat' -rl *|xargs sed -i 's/pat/rep/g'

还是我没听懂 Q 对吧?

【讨论】:

  • 它不起作用,因为我只查看某些文件(*.c、*.h、*.asm、...)
  • @coin 你可以在grep的输入列表上应用glob,在上面的例子中我使用了*
  • 你有例子吗?我可能会使用 find -name=*.a -or -name=*.b -or ... 之类的 find ,但这不是一个非常方便的解决方案
  • @coin grep -rl "pat" *.{c,h,asm,..,..,..}
  • 它不适用于我的 cygwin:$ mkdir foo; echo "bar" &gt; foo/bar.c; grep -rl 'bar' *.{c,h} grep: *.c: No such file or directory grep: *.h: No such file or directory
【解决方案5】:

我建议find2perl 如果它不能开箱即用,您可以调整它生成的代码:

find2perl /tmp \! -name ".*?\.(c|h|inc|asm|mac|def|ldf|rst)$"  -exec "sed -e s/aaa/bbb/g {}"

它将以下代码打印到标准输出:

#! /usr/bin/perl -w
    eval 'exec /usr/bin/perl -S $0 ${1+"$@"}'
        if 0; #$running_under_some_shell

use strict;
use File::Find ();

# Set the variable $File::Find::dont_use_nlink if you're using AFS,
# since AFS cheats.

# for the convenience of &wanted calls, including -eval statements:
use vars qw/*name *dir *prune/;
*name   = *File::Find::name;
*dir    = *File::Find::dir;
*prune  = *File::Find::prune;

sub wanted;
sub doexec ($@);


use Cwd ();
my $cwd = Cwd::cwd();


# Traverse desired filesystems
File::Find::find({wanted => \&wanted}, '/tmp');
exit;


sub wanted {
    my ($dev,$ino,$mode,$nlink,$uid,$gid);

    (($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_)) &&
    ! /^\..*.?\\.\(c|h|inc|asm|mac|def|ldf|rst\)\$\z/s &&
    doexec(0, 'sed -e s/aaa/bbb/g {}');
}


sub doexec ($@) {
    my $ok = shift;
    my @command = @_; # copy so we don't try to s/// aliases to constants
    for my $word (@command)
        { $word =~ s#{}#$name#g }
    if ($ok) {
        my $old = select(STDOUT);
        $| = 1;
        print "@command";
        select($old);
        return 0 unless <STDIN> =~ /^y/;
    }
    chdir $cwd; #sigh
    system @command;
    chdir $File::Find::dir;
    return !$?;
}

如果要执行,可以通过管道传给perl:

find2perl /tmp \! -name ".*?\.(c|h|inc|asm|mac|def|ldf|rst)$"  -exec "sed -e s/aaa/bbb/g" | perl

【讨论】:

    【解决方案6】:

    你可以试试这个 Vim 插件: https://github.com/skwp/greplace.vim

    基本上,它允许您输入搜索阶段(带/不带正则表达式)并询问您要搜索的文件。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-04-10
      • 2013-02-01
      • 2012-03-12
      • 1970-01-01
      • 1970-01-01
      • 2011-05-11
      相关资源
      最近更新 更多