Есть ли простой способ сделать замену текста навалом файла?
Я пытаюсь закодировать Perl script, чтобы заменить текст во всех исходных файлах моего проекта. Мне нужно что-то вроде:
perl -p -i.bak -e "s/thisgoesout/thisgoesin/gi" *.{cs,aspx,ascx}
Но это анализирует все файлы каталога рекурсивно.
Я только что начал script:
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?
Обратите внимание, что мне не нужно хранить копию каждого измененного файла; У меня есть все subversioned =)
Обновление: я пробовал это на Cygwin,
perl -p -i.bak -e "s/thisgoesout/thisgoesin/gi" {*,*/*,*/*/*}.{cs,aspx,ascx
Но похоже, что список моих аргументов взорвался до максимально допустимого размера. На самом деле, я получаю очень странные ошибки в Cygwin...
Ответы
Ответ 1
Если вы назначили @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 работал по целому файлу за раз, а не по строке.
Ответ 2
Вам может быть интересно 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 ([email protected]) {
$ft->revert;
die "update aborted: [email protected]";
}
else {
$ft->commit;
}
Пара с файлом:: Find, который вы уже написали, и вам должно быть хорошо идти.
Ответ 3
Вы можете использовать Tie:: File для масштабируемого доступа к большим файлам и изменения их на месте. См. Man-страницу (man 3perl Tie:: File).
Ответ 4
Изменить
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";
}
}
Это предполагает, что шаблон не охватывает несколько строк. Если шаблон может охватывать линии, вам нужно будет проскальзывать в содержимом файла. ( "slurp" - довольно распространенный термин Perl).
Чип на самом деле не нужен, меня просто укусили строки, которые не были chomp
ed слишком много раз (если вы отбрасываете chomp
, измените print $out "$line\n";
на print $out $line;
).
Аналогично, вы можете изменить open my $out, '>', "$f.out";
на open my $out, '>', undef;
, чтобы открыть временный файл, а затем скопировать этот файл поверх оригинала при завершении подстановки. Фактически, и особенно, если вы slurp во всем файле, вы можете просто сделать замену в памяти, а затем записать поверх исходного файла. Но я сделал достаточно ошибок, так что я всегда пишу в новый файл и проверяю содержимое.
Примечание, я изначально имел оператор if в этом коде. Скорее всего, это было неправильно. Это было бы скопировано только по строкам, которые соответствовали бы регулярному выражению "thisgoesout" (заменяя его "thisgoesin", конечно), пока молчаливо поглощали остальных.
Ответ 5
Вы можете использовать find
:
find . -name '*.{cs,aspx,ascx}' | xargs perl -p -i.bak -e "s/thisgoesout/thisgoesin/gi"
Это приведет к рекурсивному отображению всех имен файлов, затем xargs
прочитает его stdin и запустит оставшуюся часть командной строки с именами файлов, добавленными в конец. Одна приятная вещь в xargs
заключается в том, что она будет запускать командную строку более одного раза, если создаваемая ею командная строка слишком длинна для запуска за один раз.
Обратите внимание, что я не уверен, что find
полностью понимает все методы оболочки для выбора файлов, поэтому, если это не работает, попробуйте:
find . | grep -E '(cs|aspx|ascx)$' | xargs ...
При использовании таких конвейеров мне нравится создавать командную строку и запускать каждую часть отдельно, прежде чем продолжить, чтобы убедиться, что каждая программа получает желаемый вход. Таким образом, вы можете запустить часть без xargs
, чтобы проверить ее.
Мне пришло в голову, что, хотя вы этого не сказали, вы, вероятно, находитесь в Windows из-за суффиксов файлов, которые вы ищете. В этом случае вышеуказанный конвейер можно запустить с помощью Cygwin. Возможно написать Perl script, чтобы сделать то же самое, что и вы начали делать, но вам нужно будет сделать собственное редактирование самостоятельно, потому что вы не можете использовать переключатель -i
в этой ситуации.
Ответ 6
Благодаря эфемерному вопросу и на этот ответ, я получил следующее:
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!