Есть ли простой способ выполнить массовую замену текста в файле?

Я пытался написать сценарий 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 ...

Вы, вероятно, должны заметить, что используете Windows.

Brad Gilbert 30.10.2008 07:19
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
12
1
4 287
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Вы можете использовать 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: Список аргументов слишком длинный

Seiti 30.10.2008 02:01

xargs также может ограничить количество аргументов, передаваемых в каждой командной строке, если он не может определить максимальную длину командной строки. Используйте параметр -L или -n для xargs в зависимости от его версии (см. Справочную страницу).

Greg Hewgill 30.10.2008 02:03

Если вы собираетесь использовать find & xargs, используйте -print0 и -0, чтобы избежать проблем с именами файлов с пробелами. найти -print0 ... | xargs -0 ...

Schwern 30.10.2008 03:14

Изменять

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 создан именно для этого.

Schwern 30.10.2008 03:17
Ответ принят как подходящий

Если вы назначите @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 в рамках этой процедуры, поскольку эти переменные имеют довольно глобальные эффекты.

ephemient 09.07.2009 01:54

Другие вопросы по теме