Я пытался написать сценарий 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?
Обратите внимание, что мне не нужно хранить копию каждого измененного файла; Я их всех подорвал =)
Обновлять: Я пробовал это на Cygwin,
perl -p -i.bak -e "s/thisgoesout/thisgoesin/gi" {*,*/*,*/*/*}.{cs,aspx,ascx
Но похоже, что мой список аргументов увеличен до максимально допустимого размера. На самом деле я получаю очень странные ошибки на Cygwin ...





Вы можете использовать find:
find . -name '*.{cs,aspx,ascx}' | xargs perl -p -i.bak -e "s/thisgoesout/thisgoesin/gi"
Это рекурсивно перечислит все имена файлов, затем xargs прочитает свой стандартный ввод и выполнит оставшуюся часть командной строки с именами файлов, добавленными в конце. Одна хорошая вещь в xargs заключается в том, что он будет запускать командную строку более одного раза, если командная строка, которую он создает, становится слишком длинной для запуска за один раз.
Обратите внимание, что я не уверен, полностью ли find понимает все методы оболочки для выбора файлов, поэтому, если вышеперечисленное не работает, возможно, попробуйте:
find . | grep -E '(cs|aspx|ascx)$' | xargs ...
При использовании таких конвейеров мне нравится создавать командную строку и запускать каждую часть индивидуально, прежде чем продолжить, чтобы убедиться, что каждая программа получает желаемый ввод. Таким образом, вы можете сначала запустить деталь без xargs, чтобы проверить ее.
Мне просто пришло в голову, что, хотя вы этого не сказали, вы, вероятно, используете Windows из-за суффиксов файлов, которые вы ищете. В этом случае вышеуказанный конвейер можно запустить с помощью Cygwin. Можно написать сценарий Perl, который будет делать то же самое, что и вы, но вам придется выполнять редактирование на месте самостоятельно, потому что в этой ситуации вы не сможете воспользоваться преимуществами переключателя -i.
Пытался найти. -name '*. {cs, aspx, ascx}' не повезло, но версия grep перечисляла файлы. Хороший! Но когда я запускаю все команды, я получаю следующее: xargs: perl: Список аргументов слишком длинный
xargs также может ограничить количество аргументов, передаваемых в каждой командной строке, если он не может определить максимальную длину командной строки. Используйте параметр -L или -n для xargs в зависимости от его версии (см. Справочную страницу).
Если вы собираетесь использовать find & xargs, используйте -print0 и -0, чтобы избежать проблем с именами файлов с пробелами. найти -print0 ... | xargs -0 ...
Изменять
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 на самом деле не нужен, меня просто укусили строки, которые не были chomp слишком много раз (если вы уроните chomp, замените print $out "$line\n"; на print $out $line;).
Точно так же вы можете изменить open my $out, '>', "$f.out"; на open my $out, '>', undef;, чтобы открыть временный файл, а затем скопировать этот файл обратно поверх оригинала, когда замена будет выполнена. Фактически, особенно если вы проглатываете весь файл, вы можете просто произвести замену в памяти, а затем записать поверх исходного файла. Но при этом я сделал достаточно ошибок, поэтому всегда записываю в новый файл и проверяю его содержимое.
Примечание, у меня изначально был оператор if в этом коде. Скорее всего, это было неправильно. Это скопировало бы только строки, соответствующие регулярному выражению thisgoesout (конечно, заменив его на thisgoesin), а все остальное молча поглотило.
Вас могут заинтересовать 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 3 perl Tie :: File).
Да, Tie :: File создан именно для этого.
Если вы назначите @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 работал со всем файлом за раз, а не построчно.
Благодаря ephemient в этом вопросе и на этот ответ я получил следующее:
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!
Вероятно, вам следует localize @ARGV и $^I в рамках этой процедуры, поскольку эти переменные имеют довольно глобальные эффекты.
Вы, вероятно, должны заметить, что используете Windows.