以递归方式查找/替换文件,但只触摸匹配的文件

时间:2014-05-22 15:19:58

标签: regex perl search vim replace

我想在递归中快速搜索和替换文件中的 regexp 。另外,我只需要搜索特定的文件,我不想触摸与 search_pattern 不匹配的文件,否则git会认为所有已解析的文件都被修改了(这与find有关。 --exec sed)。

我尝试使用 find,grep,sed ack 在互联网上找到的许多解决方案,但我不认为他们真的很适合匹配特定文件只要。

最后我写了这个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. 人们如何应对这项琐碎的任务?几乎每个优秀的文本编辑器都有&#34;搜索和替换文件&#34;功能,但不是vim。 vim用户如何做到这一点?

  3. 编辑:

    我还尝试使用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   
    

6 个答案:

答案 0 :(得分:0)

不错,但有几个小调:

  • $ do_replace始终为0,因此不会替换
  • 就地打开F,&#34; +&gt;&#34;将不适用于cygwin + windows
  • m /($ search_pattern)/ o / o很好,()不需要。
  • $ file_pattern被忽略,您用自己的
  • 覆盖它
  • S /($ search_pattern)/($ replace_pattern)/克;

    ()是不必要的,实际上会打扰$ replace_pattern

  • 中的计数器
  • /(。+)[。](c | h | inc | asm | mac | def | ldf | rst)$ /应写成

    / \。(c | h | inc | asm | mac | def | ldf | rst)$ / and may / i also

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

  • 你不能这样做。

不知道vim,我使用emacs,它有几种方法可以实现这一点。

答案 1 :(得分:0)

以下命令有什么问题?

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

它不会导致任何VCS出现任何问题,并且是非常基本的Vimming。

你是对的,Vim并没有专门的&#34;搜索和替换文件&#34;功能,但有plugins

答案 2 :(得分:0)

为什么不呢:

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

或者我不明白Q对吗?

答案 3 :(得分:0)

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

  • 始终在每个perl脚本的顶部包含use strict;use warnings。如果您正在进行文件处理,请同时包含use autodie;
  • 继续并啜饮整个文件。这样你只需要读写一次就可以写一次。
  • 考虑使用File::Find::Rule来处理此类情况。您使用File::Find的实施工作,实际上可能是这种情况下的首选模块,但我喜欢后者的界面。
  • 我从正则表达式中删除了捕获组。 RHS中的那些是一个bug,而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, ".");   

答案 4 :(得分:0)

我建议find2perl如果它没有开箱即用,你可以调整它生成的代码:

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

它会将以下代码打印到stdout:

#! /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

答案 5 :(得分:0)

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

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