【问题标题】:perl string catenation and substitution in a single line?perl 字符串连接和替换在一行中?
【发布时间】:2017-08-27 16:12:33
【问题描述】:

我需要修改一个包含文件路径的 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/ 将不变地通过

【问题讨论】:

  • 你能举个输入/输出的例子吗?
  • 试试这个:$_ =~ s{(?:^(?!/)|(?<!/)$|/{2,})}{/}g.. 但我不知道它是否更具可读性
  • @Sobrique,通常输入字符串看起来像“home/datamonster//c2counts”或“/home/teledyne/tmp/”。我正在处理的代码是一项重新设计的工作,因此它必须能够使用现有的配置文件,这些文件的质量参差不齐;)
  • 可能值得editing 到您的问题中,以及它在输出中的外观,只是为了清楚起见。
  • 您将此标记为premature-optimisation。所以,你的主要观点是:1.)可读,2.)简洁 3.)执行速度快?只是想知道......

标签: regex string perl premature-optimization


【解决方案1】:

好吧,只是重写你得到的:

my @vars = qw ( backup_path work_path output_path );

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

我会谨慎 - 优化魔术正则表达式并不是在所有情况下都有优势,因为它们很快就会变得不可读。

上面使用hash slice mechanism 从散列中选择值(在本例中为引用),事实上s/// 无论如何都隐式地对$_ 进行操作。并在修改原始 var 时进行修改。

但是了解一下也很有用,如果您正在对包含 / 的模式进行操作,那么切换分隔符会很有帮助,因为这样您就不会得到“倾斜的牙签”效果。

s/\/{2,}/\//g可以写成:

s,/+,/,g

 s|/{2,}|/|g

如果您想保留数字量词,因为 + 本质上是 1 或更多,这在这里的工作方式相同,因为它无论如何都会将双精度数折叠成单精度数,但它在技术上匹配 @987654331 @(并用 / 替换它)原始模式没有。但是出于同样的原因,如果您的模式中有 ,,您将不想使用它。

但我认为这可以解决问题;

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

这匹配一个交替分组,替换:

  • 行首,零个或多个/
  • 字边界,零个或多个/ 行尾
  • 一个或多个斜线在其他任何地方。

使用单个 /

使用上面的散列切片机制,但没有中间'vars'。

(由于某种原因,如果没有单词边界\b 零宽度锚点,第二个分组无法正常工作 - 我认为这是一个回溯问题,但我不完全确定)

对于奖励积分 - 如果您的源数据结构合适,您可以使用 grep 选择 @vars

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/'
        };

【讨论】:

  • 这正是我正在寻找的信息!谢谢!
  • 对 \b 的需求很奇怪,不过,我同意。由于逗号被用作分隔符,\b 后面的斜线不是不必要的吗?
  • ( $_ = "/$_/" ) =~ s|/\K/+||gs|(?:/|\A|\z)/*|/|
  • 是的,我不需要用逗号分隔符来转义那个斜线。
  • 我试图了解解决方案。在正则表达式s,(?:^/*|\b\/*$|/+),/,g 中,为什么在路径末尾使用单词边界?例如你使用的是\b\/*$。这里有什么原因排除像/some/strange/path- 这样的路径吗?
【解决方案2】:

你可以这样做,但我不认为它更具可读性:

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

【讨论】:

  • 谢谢,我认为赋值语法会起作用,很高兴知道我仍然正确地记住了一些 perl!
【解决方案3】:

这个问题已经得到了很多奇妙的答案。

从非 perl 专家(我)的角度来看,有些很难阅读/理解。 ;)

所以,我可能会使用这个:

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

对我来说,这也是一年后可读的。 :)

【讨论】:

  • 可读性很重要。有时隐式变量会有所帮助,有时它们不会,所以选择对你有用的。清晰比“简洁”或“聪明”重要得多(其中大多数都已被编译器优化)
猜你喜欢
  • 1970-01-01
  • 2018-07-03
  • 2021-03-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-25
  • 2012-09-21
  • 2020-10-06
相关资源
最近更新 更多