我正在使用此命令在命令提示符中搜索并替换另一个字符串:
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.
有什么方法可以解决这个问题吗?也许是另一个命令?
答案 0 :(得分:7)
find . -name "*.txt" | xargs perl -p -i -e "s/Sitaram/Mohan/g"
find
用于递归搜索所有* .txt文件。
xargs
用于从标准输入构建和执行命令行。
答案 1 :(得分:6)
在Windows上,可以使用forfiles
命令对多个文件执行命令。 /s
选项告诉它以递归方式搜索目录。
forfiles /s /m *.txt /c "perl -pi -e s/Sitaram/Mohan/g @path"
如果需要从当前工作目录以外的地方开始搜索,请提供/p path\to\start
。
在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上的引用会有所不同 - 也许将脚本写入文件将是唯一合理的解决方案。
在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::Find
和File::Copy
是标准的Perl模块,因此所有Perl安装都有它们。