Слияние строк Perl и подстановка в одну строку?

Мне нужно изменить переменную perl, содержащую путь к файлу; он должен начинаться и заканчиваться косой чертой (/), и все экземпляры нескольких косых черт должны быть уменьшены до одной косой черты.

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

Что-то вроде этого:

foreach ( ($config->{'backup_path'},
           $config->{'work_path'},
           $config->{'output_path'}
         ) ) {
     $_ = "/" . $_ . "/";
     $_ =~ s/\/{2,}/\//g;
}

но это не выглядит оптимальным или особенно читабельным для меня; Я предпочел бы иметь более элегантное выражение (если оно закончится использованием необычного регулярного выражения, я воспользуюсь комментарием, чтобы сделать его более понятным).

Примеры ввода и вывода

home/datamonster//c2counts становится /home/datamonster/c2counts/

home/////teledyne/tmp/ становится /home/teledyne/tmp/

а также /var/backup/DOC/all_instruments/ пройдет без изменений

3 ответа

Решение

Ну, просто переписать то, что вы получили:

my @vars = qw ( backup_path work_path output_path );

for ( @{$config}{@vars} ) {
   s,^/*,/,;  #prefix
   s,/*$,/,; #suffix
   s,/+,/,g; #double slashes anywhere else. 
}

Я был бы осторожен - оптимизация для магических регулярных выражений не является преимуществом в каждой ситуации, потому что они становятся довольно быстро нечитаемыми.

Выше используется механизм среза хеша для выбора значений из хеша (ссылка в данном случае) и тот факт, что s/// неявно действует на $_ тем не мение. И изменяет оригинальный var, когда это делает.

Но также полезно знать, работаете ли вы с шаблонами, содержащими / полезно поменять разделители, потому что таким образом вы не получите эффект "наклоняющиеся зубочистки".

s/\/{2,}/\//g можно записать как:

s,/+,/,g

или же

 s|/{2,}|/|g

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

Однако я думаю, что это делает трюк;

s,(?:^/*|\b\/*$|/+),/,g for @{$config}{qw ( backup_path work_path output_path )};

Это соответствует альтернативной группе, заменяющей либо:

  • начало строки, ноль или более /
  • граница слова, ноль или более / конец линии
  • одна или несколько косых черт в другом месте.

с одним /,

использует механизм среза хеша, как описано выше, но без промежуточных 'vars'.

(По какой-то причине вторая группа не работает правильно без границы слова \b якорь нулевой ширины - я думаю, что это проблема возврата, но я не совсем уверен)

Для бонусных баллов - вы можете выбрать @vars с помощью grep если ваша исходная структура данных соответствует:

my @vars = grep { /_path$/ } keys %$config; 
#etc. Or inline with:
s,(?:^/*|\b\/*$|/+),/,g for @{$config}{grep { /_path$/ } keys %$config };

Редактировать: Или, как отмечает Borodin:

s|(?:/|\A|\z)/*|/|

Давать нам:

#!/usr/bin/env perl

use strict;
use warnings;
use Data::Dumper;

my $config = {
   backup_path => "/fish/",
   work_path   => "narf//zoit",
   output_path => "/wibble",
   test_path => 'home/datamonster//c2counts',
   another_path => "/home/teledyne/tmp/",
   again_path => 'home/////teledyne/tmp/',
   this_path => '/var/backup/DOC/all_instruments/',
};

s,(?:/|\A|\b\z)/*,/,g for @{$config}{grep { /_path$/ } keys %$config };

print Dumper $config;

Результаты:

$VAR1 = {
          'output_path' => '/wibble/',
          'this_path' => '/var/backup/DOC/all_instruments/',
          'backup_path' => '/fish/',
          'work_path' => '/narf/zoit/',
          'test_path' => '/home/datamonster/c2counts/',
          'another_path' => '/home/teledyne/tmp/',
          'again_path' => '/home/teledyne/tmp/'
        };

Вы могли бы сделать это так, но я бы не назвал это более читабельным:

foreach ( ($config->{'backup_path'},
           $config->{'work_path'},
           $config->{'output_path'}
         ) ) {
     ( $_ = "/$_/" ) =~ s/\/{2,}/\//g;
}

Этот вопрос уже получил много фантастических ответов.

С точки зрения non-perl-expert (me), некоторые трудно читать / понимать.;)

Итак, я бы, вероятно, использовал это:

my @vars = qw ( backup_path work_path output_path );
for my $var (@vars) {
    my $value = '/' . $config->{$var} . '/';
    $value =~ s|//+|/|g;
    $config->{$var} = $value;
}

Для меня это будет читаться через год тоже.:)

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