Рекурсивный поиск и замена usind Perl в cmd (Windows) - PullRequest
6 голосов
/ 30 марта 2011

Я использую эту команду для поиска и замены строки другой в командной строке:

 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 ]

7 голосов
/ 20 декабря 2011

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

find используется для рекурсивного поиска всех * .txt файлов.

xargs используется для построения и выполнения командных строк из стандартного ввода.

6 голосов
/ 16 июня 2014

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, указанной выше. Он должен проверить, заканчивается ли имя файла .txt и не является ли каталогом, создать его резервную копию и перезаписать исходный файл, изменив версию резервной копии.

Цитирование будет отличаться в Windows - возможно, запись сценария в файл будет единственным разумным решением.

Проблемы с оригинальным подходом OP

В оболочке Unix шаблоны глобусов (например, *.txt) расширяются оболочкой, тогда как Windows cmd оставляет их нетронутыми и передает их непосредственно вызываемой программе. Это его работа, чтобы справиться с ними. Perl не может сделать это очевидно.

Вторая проблема заключается в том, что даже в Unix глобализация не работает должным образом. *.txt - это все .txt файлы в текущем каталоге, кроме файлов в подкаталогах и их подкаталогах ...

1 голос
/ 16 июня 2014

Если вы собираетесь беспокоиться о Perl, почему бы просто не сделать все возможное и написать (короткую) программу на Perl, чтобы сделать это для вас?

Таким образом, вы не будете выдавать ее междуоболочка и ваша программа, и у вас есть что-то более универсальное и может работать в нескольких операционных системах.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...