Требуется редактирование файлов на месте Perl не в командной строке

#perl #file #in-place

#perl #файл #на месте

Вопрос:

У меня есть программа, в которой внутренне настроено несколько имен файлов. Программа редактирует кучу файлов конфигурации, связанных с учетной записью базы данных, а затем изменяет пароль базы данных для учетной записи базы данных.

Список файлов конфигурации связан с именем учетной записи базы данных через внутренний список. Когда я обрабатываю эти файлы, в моей программе возникает следующий цикл:

 BEGIN { $^I = '.oldPW'; }  # Enable in-place editing
...
foreach (@{$Services{$request}{'files'}})
{
    my $filename = $Services{$request}{'configDir'} . '/' . $_;
    print "Processing ${filename}n";
    open CONFIGFILE, ' <', $filename or warn $!;
    while (<CONFIGFILE>)
    {
        s/$oldPass/$newPass/;
        print;
    }
    close CONFIGFILE;
}
  

Проблема в том, что измененный вывод записывается в стандартный вывод, а не в ФАЙЛ конфигурации. Как мне заставить это на самом деле редактировать на месте? Переместить $ ^ I внутри цикла? Распечатать ФАЙЛ конфигурации? Я в тупике.

>

Обновление: Я нашел то, что искал в PerlMonks. Вы можете использовать локальный ARGV внутри цикла, чтобы выполнять редактирование на месте обычным способом Perl. Приведенный выше цикл теперь выглядит следующим образом:

 foreach (@{$Services{$request}{'files'}})
{
    my $filename = $Services{$request}{'configDir'} . '/' . $_;
    print "Processing ${filename}n";
    {
        local @ARGV = ( $filename);
        while (<>)
        {
            s/$oldPass/$newPass/;
            print;
        }
    }
}
  

Если бы не привязка configDir к началу, я мог бы просто поместить весь список в локальный @ARGV, но это достаточно эффективно.

Спасибо за полезные предложения по Tie::File . Я бы, вероятно, пошел этим путем, если бы делал это заново. Файлы конфигурации, которые я редактирую, никогда не превышают нескольких КБ в длину, поэтому привязка не будет использовать слишком много памяти.

Ответ №1:

Последние версии File::Slurp предоставляют удобные функции, edit_file и edit_file_lines . Внутренняя часть вашего кода будет выглядеть:

 use File::Slurp qw(edit_file);
edit_file { s/$oldPass/$newPass/g } $filename;
  

Комментарии:

1. Не забудьте сделать резервную копию вашего файла.

Ответ №2:

$^I Переменная работает только с последовательностью имен файлов, хранящихся в, $ARGV используя пустую <> конструкцию. Возможно, что-то вроде этого сработало бы:

 BEGIN { $^I = '.oldPW'; }  # Enable in-place editing
...

local @ARGV = map {
    $Services{$request}{'configDir'} . '/' . $_ 
} @{$Services{$request}{'files'}};
while (<>) {
   s/$oldPass/$newPass/;

   # print? print ARGVOUT? I don't remember
   print ARGVOUT;
}
  

но если это не простой скрипт и вам нужен @ARGV и STDOUT для других целей, вам, вероятно, лучше использовать что-то вроде Tie::File для этой задачи:

 use Tie::File;
foreach (@{$Services{$request}{'files'}})
{
    my $filename = $Services{$request}{'configDir'} . '/' . $_;

    # make the backup yourself
    system("cp $filename $filename.oldPW");   # also consider File::Copy

    my @array;
    tie @array, 'Tie::File', $filename;

    # now edit @array
    s/$oldPass/$newPass/ for @array;

    # untie to trigger rewriting the file
    untie @array;
}
  

Комментарии:

1. На самом деле мне не нужны резервные копии, потому что эти файлы хранятся в системе управления исходным кодом. Следующим шагом в этом скрипте является интеграция SCCS (в данном случае, принудительно) в скрипт, чтобы он проверял и отправлял файлы по мере их обновления.

Ответ №3:

Tie::File уже упоминалось, и это очень просто. Избегать переключения -i, вероятно, хорошая идея для сценариев, отличных от командной строки. Если вы хотите избежать Tie:: File, стандартное решение таково:

  • Откройте файл для ввода
  • Откройте временный файл для вывода
  • Прочитайте строку из входного файла.
  • Измените строку любым удобным для вас способом.
  • Запишите новую строку в свой временный файл.
  • Переход к следующей строке и т.д.
  • Закройте входные и выходные файлы.
  • Переименуйте входной файл в какое-нибудь имя резервной копии, например, добавив .bak к имени файла.
  • Переименуйте временный выходной файл в исходное входное имя файла.

По сути, это то, что происходит за кулисами с переключателем -i.bak в любом случае, но с дополнительной гибкостью.