在cmd中使用Perl进行递归搜索和替换(Windows)

时间:2011-03-30 06:51:29

标签: perl cmd

我正在使用此命令在命令提示符中搜索并替换另一个字符串:

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

这会将Mohan替换为当前目录中Sitaram文件中的ab.txt

但是,我希望在所有子目录(递归)中的所有Mohan文件中用Sitaram替换所有出现的.txt。使用*.txt代替ab.txt不起作用。正则表达式工作,因为我已下载Windows的正则表达式包。它不仅适用于此命令

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

有什么方法可以解决这个问题吗?也许是另一个命令?

3 个答案:

答案 0 :(得分:7)

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

find用于递归搜索所有* .txt文件。

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

答案 1 :(得分: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,它将标准输入的行作为参数传递给给定的命令。使用.txt命令递归搜索目录find个文件。

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

独立于平台的解决方案

您还可以在Perl中编码搜索文件和字符串替换。 File::Find核心模块可以提供帮助。 (核心模块=与解释器一起分发。)

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

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

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

OP原始方法的问题

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

第二个问题是,即使在Unix下,globbing也无法正常工作。 *.txt是当前目录中的所有.txt个文件,不包括子目录及其子目录中的文件......

答案 2 :(得分: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安装都有它们。