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

发布于 2024-10-27 21:15:50 字数 584 浏览 1 评论 0原文

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

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

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

但是,我想将所有子目录中的所有 .txt 文件中所有出现的 Mohan 替换为 Sitaram (递归地)。使用 *.txt 代替 ab.txt 不起作用。正则表达式可以正常工作,因为我已经下载了 Windows 的正则表达式包。它不仅仅适用于这个命令,说“

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

有什么方法可以解决这个问题吗?”也许是不同的命令?

I am using this command to search and replace a string with another in the command prompt:

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

This replaces Mohan with Sitaram in the file ab.txt in the current directory.

However I want to replace all occurrences of Mohan with Sitaram in all .txt files in all the sub-directories (recursively). Using *.txt instead of ab.txt doesn’t work. Regular expressions work otherwise as I have downloaded the regex packages for Windows. It doesn’t work only for this command saying

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

Is there any way to fix this? A different command perhaps?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

最单纯的乌龟 2024-11-03 21:15:50

<代码>查找 . -名称“*.txt”| xargs perl -p -i -e "s/Sitaram/Mohan/g"

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

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

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

find is used to search all *.txt files recursively.

xargs is used to build and execute command lines from standard input.

笔落惊风雨 2024-11-03 21:15:50

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 下,通配符也无法按预期工作。 *.txt 是当前目录下的所有.txt 文件,不包括子目录及其子目录中的文件...

Windows solution

On Windows, a command can be executed for multiple files using the forfiles command. The /s option tells it to search directories recursively.

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

If starting the search from other than the current working directory is desired, supply /p path\to\start.

Unix solution

On Unix, there is a more generic command than forfiles called xargs, which passes lines of its standard input as arguments to the given command. Directories are searched recursively for .txt files using the find command.

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

Platform-independent solution

You can also code both the search for files and string replacement in Perl. The File::Find core module can help with that. (Core module = distributed with the interpreter.)

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

However the Perl code will be longer and I don’t want to spend time writing it. Implement the sub yourself using info from the File::Find manpage linked above. It should test if the file name ends with .txt and is not a directory, create its backup and rewrite the original file by the changed version of the backup.

The quoting will differ on Windows – perhaps writing the script into a file will be the only sane solution there.

Problems with OP’s original approach

In Unix shell, glob patterns (e.g. *.txt) are expanded by the shell, whereas Windows cmd leaves them untouched and passes them right to the program being invoked. It is its job to handle them. Perl cannot do that obviously.

Second problem is that even under Unix, globbing would not work as desired. *.txt are all .txt files in the current directory, not including those in subdirectories and their subdirectories…

离笑几人歌 2024-11-03 21:15:50

如果您打算使用 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 安装都包含它们。

If you're going to bother with Perl, why not simply go all out and write a (short) Perl program to do this for you?

This way, you're not passing it off between the shell and your program, and you have something that's more universal and can run on multiple operating systems.

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

I use File::Find to gather all of the files that I want to modify in my @files array. I could have put the whole thing inside the find subroutine:

 find(\&wanted, $directory);

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

The whole program is in the wanted subroutine this way. It's more efficient because I'm now replacing as I am finding the files. No need to go through first, find the files, then do the replacement. However, it strikes me as bad design.

You can also slurp your entire file into an array without looping through it at first:

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

Now, you can use grep to check to see if there's anything that needs to be replaced:

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
}

This is more efficient (no need to do a replace unless that file has the string you're looking for). However, it takes up memory (the entire file must be in memory at one time), and don't let that one line fool you. The entire file is still read into that array one line at a time as if you did an entire loop.

The File::Find and File::Copy are standard Perl modules, so all Perl installations have them.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文