Есть ли простой способ выполнить массовую замену текста в файле?
-
05-07-2019 - |
Вопрос
Я пытался написать сценарий Perl, чтобы заменить текст во всех исходных файлах моего проекта.Мне нужно что-то вроде:
perl -p -i.bak -e "s/thisgoesout/thisgoesin/gi" *.{cs,aspx,ascx}
Но это анализирует все файлы каталога рекурсивно.
Я только что запустил скрипт:
use File::Find::Rule;
use strict;
my @files = (File::Find::Rule->file()->name('*.cs','*.aspx','*.ascx')->in('.'));
foreach my $f (@files){
if ($f =~ s/thisgoesout/thisgoesin/gi) {
# In-place file editing, or something like that
}
}
Но теперь я застрял.Есть ли простой способ редактировать все файлы с помощью Perl?
Обратите внимание, что мне не нужно хранить копию каждого измененного файла;Я их всех перевернул =)
Обновлять:Я попробовал это на Сигвин,
perl -p -i.bak -e "s/thisgoesout/thisgoesin/gi" {*,*/*,*/*/*}.{cs,aspx,ascx
Но похоже, что мой список аргументов увеличился до максимально допустимого размера.На самом деле, я получаю очень странные ошибки в Cygwin...
Решение
Если вы назначите @ARGV
перед использованием *ARGV
(он же ромб <>
), $^I
/ -i
будет работать с этими файлами вместо того, что было указано в командной строке.
use File::Find::Rule;
use strict;
@ARGV = (File::Find::Rule->file()->name('*.cs', '*.aspx', '*.ascx')->in('.'));
$^I = '.bak'; # or set `-i` in the #! line or on the command-line
while (<>) {
s/thisgoesout/thisgoesin/gi;
print;
}
Это должно делать именно то, что вы хотите.
Если ваш шаблон может занимать несколько строк, добавьте undef $/;
перед <=>, чтобы Perl обрабатывал весь файл за раз, а не построчно.
Другие советы
Вас может заинтересовать File :: Transaction :: Atomic или File :: Transaction
ОПИСАНИЕ для F :: T :: A очень похоже на то, что вы пытаетесь сделать:
# In this example, we wish to replace
# the word 'foo' with the word 'bar' in several files,
# with no risk of ending up with the replacement done
# in some files but not in others.
use File::Transaction::Atomic;
my $ft = File::Transaction::Atomic->new;
eval {
foreach my $file (@list_of_file_names) {
$ft->linewise_rewrite($file, sub {
s#\bfoo\b#bar#g;
});
}
};
if ($@) {
$ft->revert;
die "update aborted: $@";
}
else {
$ft->commit;
}
Соедините это с File :: Find, который вы уже написали, и вам пора.
Вы можете использовать Tie :: File для масштабируемого доступа к большим файлам и их изменения на месте. Смотрите man-страницу (man 3perl Tie :: File).
Изменять
foreach my $f (@files){
if ($f =~ s/thisgoesout/thisgoesin/gi) {
#inplace file editing, or something like that
}
}
К
foreach my $f (@files){
open my $in, '<', $f;
open my $out, '>', "$f.out";
while (my $line = <$in>){
chomp $line;
$line =~ s/thisgoesout/thisgoesin/gi
print $out "$line\n";
}
}
Это предполагает, что шаблон не занимает несколько строк.Если шаблон может занимать строки, вам придется проглотить содержимое файла.(«хлебнуть» — довольно распространенный термин Perl).
На самом деле чавканье не обязательно, меня просто укусили строки, которых не было. chomp
повторял слишком много раз (если вы уроните chomp
, изменять print $out "$line\n";
к print $out $line;
).
Аналогично, вы можете изменить open my $out, '>', "$f.out";
к open my $out, '>', undef;
чтобы открыть временный файл, а затем скопировать этот файл обратно поверх оригинала, когда замена будет завершена.На самом деле, особенно если вы хлебаете весь файл, вы можете просто сделать подстановку в памяти, а затем переписать исходный файл.Но я совершил при этом достаточно ошибок, поэтому всегда записываю в новый файл и проверяю его содержимое.
Примечание, изначально в этом коде у меня был оператор if.Скорее всего, это было неправильно.Это означало бы копирование только строк, соответствующих регулярному выражению «thisgoesout» (конечно, с заменой его на «thisgoesin»), молча поглощая все остальное.
Вы можете использовать find
:
find . -name '*.{cs,aspx,ascx}' | xargs perl -p -i.bak -e "s/thisgoesout/thisgoesin/gi"
Это будет рекурсивно перечислять все имена файлов, затем xargs
прочитает его стандартный ввод и запустит оставшуюся часть командной строки с именами файлов, добавленными в конце. Одна из замечательных особенностей -i
заключается в том, что он будет запускать командную строку более одного раза, если создаваемая командная строка становится слишком длинной для запуска за один раз.
Обратите внимание, что я не уверен, что <=> полностью понимает все методы оболочки для выбора файлов, поэтому, если приведенное выше не работает, возможно, попробуйте:
find . | grep -E '(cs|aspx|ascx)$' | xargs ...
При использовании подобных конвейеров мне нравится создавать командную строку и запускать каждую часть отдельно, прежде чем продолжить, чтобы убедиться, что каждая программа получает требуемый ввод. Таким образом, вы можете запустить часть без <=> первой проверки.
Мне просто пришло в голову, что, хотя вы этого не сказали, вы, вероятно, работаете в Windows из-за искомых суффиксов файлов. В этом случае вышеуказанный конвейер может быть запущен с использованием Cygwin. Можно написать Perl-скрипт, который будет выполнять то же самое, что и вы, но вы должны будете выполнить редактирование на месте самостоятельно, потому что вы не можете воспользоваться преимуществом переключателя <=> в этой ситуации. р>
Спасибо Эфименту по этому вопросу и по этой ответ , я получил это:
use File::Find::Rule;
use strict;
sub ReplaceText {
my $regex = shift;
my $replace = shift;
@ARGV = (File::Find::Rule->file()->name('*.cs','*.aspx','*.ascx')->in('.'));
$^I = '.bak';
while (<>) {
s/$regex/$replace->()/gie;
print;
}
}
ReplaceText qr/some(crazy)regexp/, sub { "some $1 text" };
Теперь я могу даже перебирать хеш, содержащий regexp = > subs записи!