2011-03-30 59 views
6

我使用这个命令来搜索并与另一个在命令提示符下替换字符串:递归搜索,并在cmd中替换usind的Perl(Windows)中

perl -pi -i.bak -e "s/Mohan/Sitaram/g" ab.txt 

这将替换MohanSitaram文件ab.txt在当前目录。

但是,我想在所有子目录(递归)中的所有.txt文件中替换所有出现的MohanSitaram。使用*.txt而不是ab.txt不起作用。正则表达式正常工作,因为我已经下载了Windows的正则表达式包。它不工作只为这个命令说

E:\>perl -pi -e "s/Sitaram/Mohan/g" *.txt 
Can't open *.txt: Invalid argument. 

有没有什么办法解决这个问题?也许有不同的命令?

回答

7

find . -name "*.txt" | xargs perl -p -i -e "s/Sitaram/Mohan/g"

find来搜索所有* .TXT文件递归。

xargs用于构建和执行标准输入的命令行。

6

的Windows解决方案

在Windows上,一个命令可以使用forfiles命令多个文件来执行。 /s选项告诉它递归搜索目录。

forfiles /s /m *.txt /c "perl -pi -e s/Sitaram/Mohan/g @path" 

如果启动由当前的工作目录以外的搜索需要,供应/p path\to\start

Unix的溶液

在Unix,有一个更通用的命令比forfiles称为xargs,其通过它的标准输入的线作为参数来给定的命令。使用find命令递归地搜索目录中的.txt文件。

find . -name '*.txt' | xargs perl -pi -e 's/Sitaram/Mohan/g' 

独立于平台的解决方案

您也可以编写既为文件的搜索和替换字符串在Perl。核心模块File::Find可以帮助解决这个问题。 (核心模块=与解释器一起发布)

perl -MFile::Find -e 'find(sub{…}, ".")' 

但是,Perl代码会更长,我不想花时间写它。使用上面链接的File::Find联机帮助页中的信息自己实现子部分。它应该测试文件名是否以.txt结尾,并且不是目录,请创建其备份并通过更改后的备份版本重写原始文件。

引用在Windows上会有所不同 - 也许将脚本写入文件将是唯一的解决方案。

OP的原始方法的问题

在Unix shell中,glob模式(例如,*.txt)由shell扩展,而Windows cmd保持不变,并将它们传递给正在调用的程序。处理它们是它的工作。 Perl无法做到这一点。

第二个问题是,即使在Unix下,globbing也不能按需要工作。 *.txt是在当前目录中,不包括那些在子目录及其子目录下的所有文件.txt ...

+0

关于独立于平台的解决方案...我昨天写了一段代码[File :: Find'](http://stackoverflow.com/a/24634420/2157640)。你可以用它来了解它的外观。 – Palec 2014-07-09 10:29:13

1

如果你打算用Perl打扰,何不干脆全力以赴,写(短)的Perl程序来做到这一点为你?

这样,你不会在shell和你的程序之间传递它,而你拥有更通用的东西,并且可以在多个操作系统上运行。

#!/usr/bin/env perl <-- Not needed for Windows, but tradition rules 
use strict; 
use warnings; 
use feature qw(say); 
use autodie;   # Turns file operations into exception based programming 

use File::Find;  # Your friend 
use File::Copy;  # For the "move" command 

# You could use Getopt::Long, but let's go with this for now: 

# Usage = mungestrings.pl <from> <to> [<dir>] 
#   Default dir is current 
# 
my $from_string = shift; 
my $to_string = shift; 
my $directory = shift; 

$from_string = quotemeta $from_string; # If you don't want to use regular expressions 

$directory = "." if not defined $directory; 

# 
# Find the files you want to operate on 
# 
my @files; 
find(
    sub { 
     return unless -f;  # Files only 
     return unless /\.txt$/ # Name must end in ".txt" 
     push @files, $File::Find::name; 
    }, 
    $directory 
); 

# 
# Now let's go through those files and replace the contents 
# 

for my $file (@files) { 
    open my $input_fh, "<", $file; 
    open my $output_fh, ">" "$file.tmp"; 
    for my $line (<$input_fh>) { 
     $line =~ s/$from_string/$to_string/g; 
     print ${output_fh} $line; 
    } 

    # 
    # Contents been replaced move temp file over original 
    # 
    close $input_fh; 
    close $output_fh; 
    move "$file.tmp", $file; 
} 

我用File::Find收集所有,我想在我的@files数组来修改文件。我可以把整个事情的find子程序内:

find(\&wanted, $directory); 

sub wanted { 
    return unless -f; 
    return unless /\.txt/; 
    # 
    # Here: open the file for reading, open output and move the lines over 
    # 
    ... 
} 

整个程序是在wanted子程序这种方式。它更高效,因为我现在正在替换,因为我正在查找这些文件。无需先通过,找到文件,然后进行更换。然而,它让我觉得糟糕的设计。

您还可以发出声音的整个文件到一个数组,而无需通过其循环的第一:

open my $input_fh, "<", $file; 
@input_file = <$input_fh>; 

现在,你可以使用grep进行检查,看是否有任何需要更换:

if (grep { $from_string } @input_file) { 
    # Open an output file, and do the loop to replace the text 
} 
else { 
    # String not here. Just close up the input file 
    # and don't bother with writing a new one and moving it over 
} 

这样更有效率(不需要做替换,除非该文件包含您正在查找的字符串)。但是,它占用了内存(整个文件一定在内存中),不要让这一行欺骗你。整个文件仍然一次一行地读入该数组中,就像您执行了整个循环一样。

File::FindFile::Copy是标准的Perl模块,所以所有的Perl安装都有它们。