skasch has asked for the wisdom of the Perl Monks concerning the following question:

Dear Monks,

Basically the same as my previous post but with a slightly modified problem, so content is mostly the same but this time perl does not replace what i want

I am a beginner with Perl and seek wisdom of the monks

What i want is to read a file, run a regex on its lines and when matching substitute some strings according to a map.

Mostly that does work but on a specific line, i cannot get my replacement to replace and I like to understand why

This is an excerpt of one of the files that should be processed

"user.name@domain.com:Calendar/personal" = <*I0>; }; SubscribedFolders = ( "user@domain.com:Calendar/BCA-513DD600-1B-6967B200" ); FoldersOrder = ( personal, "user_A_domain_D_com_BCA-513DD600-1B-6967B200", "7D03-5682B480-975-5FFE8000", "7D03-5682B480-977-5FFE8000" ); FreeBusyExclusions = { "user.name@domain.com:Calendar/personal" = <*I0>; "user@domain.com:Calendar/BCA-513DD600-1B-6967B200" = <*I1>;

And this is my Code

#!/usr/bin/perl use strict; use warnings; use autodie; my %replacements = ( 'user.name@domain.com' => 'uname', 'user@domain.com' => 'user', # this is a new replacement '_A_domain_D_com_' => '_', ); open( my $readFile, '<', "sampleFile" ); while ( <$readFile> ) { # if contains :Calendar and is suffixed with / # or :Contacts with same suffix or Users prefixed # with / or is an email-address followed by " = if ( m/:Calendar(?=\/)/ or m/:Contacts(?=\/)/ or m/(?<=\"\/)Users/ or m/.+@.+\"\s=/ or m/_A_albertbauer_D_com/ ) { # then replace every occurrence as in list foreach my $key ( sort keys %replacements ) { s/\b$key\b/$replacements{$key}/g; } } print $_; }

And this is the result

"uname:Calendar/personal" = <*I0>; }; SubscribedFolders = ( "user@domain.com:Calendar/BCA-513DD600-1B-6967B200" ); FoldersOrder = ( personal, "user_A_domain_D_com_BCA-513DD600-1B-6967B200", "7D03-5682B480-975-5FFE8000", "7D03-5682B480-977-5FFE8000" ); FreeBusyExclusions = { "uname:Calendar/personal" = <*I0>; "user:Calendar/BCA-513DD600-1B-6967B200" = <*I1>;

I do not understand why the replacement under "user_A_domain_D_com_BCA-513DD600-1B-6967B200" does not happen as it is defined in the mappings... I would expect the output to transform into this: "user_BCA-513DD600-1B-6967B200" when I write my "foreach" like this:

foreach my $key ( sort keys %replacements ) { s/\b$key\b/$replacements{$key}/g; print "$key $replacements{$key}\n"; }

I can see that the replacement is done but i does not print in $_

any help is greatly appreciated

cheers, Sascha

Replies are listed 'Best First'.
Re: Cannot get Perl to replace a specific string in my textfile
by haukex (Archbishop) on Jan 13, 2017 at 15:52 UTC

    Hi skasch,

    Assuming that difference in the two strings pointed out in the AM post is just a copy/paste mistake and you meant to say "_A_domain_D_com" instead of "_A_albertbauer_D_com", the reason why your code isn't working as intended is \b. This matches the boundary between a word (\w) and non-word (\W) character. \w is discussed in detail here, but to simplify, under ASCII (/a) it's the set [a-zA-Z0-9_]. Note how it includes letters, digits, and the underscore. Therefore, the string user_A_domain_D_com_BCA contains no internal word bounaries, and the regex you are trying to match it against, /\b_A_domain_D_com_\b/ will not match that string.

    Having said that, note how in your solution you're having to maintain two sets of regexes: %replacements and the long if statement. While it's possible this is necessary due to the structure of the data (I don't know all your requirements), note that there are ways to express the same stuff without the repetition. Here's one way to dynamically build the regex:

    my %replacements = (...); my $regex = join '|', map {quotemeta} sort { length $b <=> length $a } keys %replacements; $regex = qr/$regex/; # ... while ( <$readFile> ) { s/($regex)/$replacements{$1}/g; print $_; }

    And if you need to check for the presence of the strings "Calendar" / "Contacts" / "Users", as in your example code, you can combine the multiple regexes into something like /:(?:Calendar|Contacts)(?=\/)|(?<=\"\/)Users/ (Update: shortened regex slightly).

    One more thing: Mind the Meta! Note how the %replacements key user.name@domain.com contains two dots - in your current solution, those will match any character. Only if you say \Q$key\E (or manually use quotemeta as I showed above) will the dots and any other special regex chars be matched literally.

    Hope this helps,
    -- Hauke D

Re: Cannot get Perl to replace a specific string in my textfile
by Anonymous Monk on Jan 13, 2017 at 12:48 UTC
    The replacement only happens when the input contains the string "_A_albertbauer_D_com", which it doesn't.
Re: Cannot get Perl to replace a specific string in my textfile
by 1nickt (Canon) on Jan 13, 2017 at 12:46 UTC

    In the code you've shown, you don't need s/.../.../

    $key = $replacements{$key};

    The way forward always starts with a minimal test.

      Hi 1nickt,

      In the code you've shown, you don't need s/.../.../

      $key = $replacements{$key};

      I'm not sure I'm understanding your post...? This doesn't work:

      foreach my $key ( sort keys %replacements ) { $key = $replacements{$key}; }

      Regards,
      -- Hauke D

Re: Cannot get Perl to replace a specific string in my textfile
by skasch (Novice) on Jan 14, 2017 at 17:46 UTC

    Hi haukex,

    you are right it was a typo.

    Thank you very much for your insights regarding the word boundaries and also those on literally matching, did not take it into account so far.

    I changed both in my code and now it works as intended, I do not yet fully understand to build a regex dynamically though. But i do understand the possiblity to write more elegant code

    so finally still with repetition and static ;-)

    if ( m/:Calendar(?=\/)/ or m/:Contacts(?=\/)/ or m/(?<=\"\/)Users/ or m/.+@.+\"\s=/ or m/_A_domain_D_com/ ) { foreach my $key ( sort keys %replacements ) { s/\Q$key\E/$replacements{$key}/g; } }

    Indeed that helped Hauke, thanks!

    Sascha