Perl string catenation and substitution in one line?

I need to change the perl variable containing the file path; it must start and end with a forward slash (/) and have all instances of the multiple forward slash flattened to one forward slash.

(This is because the existing process does not provide consistent configuration syntax, so hundreds of configuration files are scattered all over the place, which may or may not have slashes in the correct places in filenames and pathnames.)

Something like that:

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

      

but that doesn't look optimal or particularly readable to me; I'd rather have a more elegant expression (if it ends up using an unusual regex, I'll use a comment to make it clearer.)

Input and output examples

home/datamonster//c2counts

becomes /home/datamonster/c2counts/

home/////teledyne/tmp/

becomes /home/teledyne/tmp/

and /var/backup/DOC/all_instruments/

will pass unchanged

+1


source to share


3 answers


Well, just by rewriting what you got:

my @vars = qw ( backup_path work_path output_path );

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

      

I'd be careful - optimizing for magic regex is not an advantage in every situation, because they get unreadable pretty quickly.

The above example uses the hash segment mechanism to select values ​​from the hash (link in this case) and the fact that s///

implicitly acts on $_

anyway. And changes the original var when it does so.

But it's also good to know if you are working with templates containing /

, it is useful to toggle separators, because that way you don't get the "leaning toothpicks" effect.

s/\/{2,}/\//g

can be written as:

s,/+,/,g

      

or

 s|/{2,}|/|g

      

if you want to keep the numeric quantifier as +

inherently 1 or more, which works the same here because it collapses the double into one anyway, but technically matches /

(and replaces it with /

) where the original pattern is missing. But you don't want to use ,

it if you have it in your template, for the same reason.

However, I think this does the trick;

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

      

This matches the grouping of alternation, replacing either:



  • start of line, zero or more /

  • word boundary, zero or more /

    end of line
  • one or more slashes elsewhere.

with one /

.

uses the hash slice mechanism as above, but without the intermediate "vars".

(For some reason, the second grouping does not work correctly without word boundary \b

zero-width anchors - I think this is a backtracking issue, but I'm not entirely sure)

For bonus points, you can choose @vars

with grep

if your original data structure fits:

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

      

Edit: or as Borodin notes:

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

      

Providing us:

#!/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;

      

Results:

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

      

+2


source


you can do it like this, but I wouldn't call it more readable:



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

      

+1


source


This question has already received many fantastic answers.

From a non-perl-expert's point of view (me), some are difficult to read / understand.;)

So, I would probably use this:

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

      

It will be available for me after a year. :)

0


source







All Articles