perl: substitute pattern with pattern of different size

317 views Asked by At

here is my string A B C D and I want to replace A by 123 and C by 456 for example. However this doesn't work.

$string=~ s/A|B|C|D/123|B|456|D/;

I want this 123 B 456 D but i get this 123|B|456|D B C D

Probably because the number of characters in my two patterns is different.

Is there a way to substitute patterns of different size using some other piece of code ? Thanks a lot.

4

There are 4 answers

2
AudioBubble On BEST ANSWER

Something like this using eval (untested).

$string=~ s/(A)|C/ length($1) ? '123': '456'/eg;  

Using the eval flag in the s/// form means to evaluate the replacement
side as a line of code that returns a value.

In this case it executes a ternary conditional in the replacement code.

It's sort of like an inline regex callback.
It's much more complicated though since it can be like s///eeg so
better to refer to the docs.

Remember, eval is really evil, misspelled !!

1
Axeman On

You're getting what I'd expect you to get. Your regex looks for one occurrence of either an 'A' or a 'B' or a 'C' or a 'D' and replaces it with the literal string '123|B|456|D'. Hence 'A B C D' -> '123|B|456|D B C D'

So it finds the first occurrence, the 'A' and replaces it with the string you specified. An alternation matches various strings, but the pipe characters mean nothing in the replacement slot.

What you need to do is create a mapping from input to output, like so:

my %map = ( A => '123', C => '456' );

Then you need to use it in the replacements. Let's give you a search expression:

my $search = join( '|', keys %map );

Now let's write the substitution (I prefer braces when I code substitutions that have code in them:

$string =~ s{($search)}{ $map{$1} }g;

The g switch means we match every part of the string we can, and the e switch tells Perl to evaluate the replacement expression as Perl code.

The output is '123 B 456 D'

0
Ilmari Karonen On

The easiest way to do this is with two substitutions:

$string =~ s/A/123/g;
$string =~ s/B/456/g;

or even (using an inline for loop as a shorthand to apply multiple substitutions to one string):

s/A/123/g, s/B/456/g for $string;

Of course, for more complicated patterns, this may not produce the same results as doing both substitutions in one pass; in particular, this may happen if the patterns can overlap (as in A = YZ, B = XY), or if pattern B can match the string substituted for pattern A.

If you wish to do this in one pass, the most general solution is to use the /e modifier, which causes the substitution to be interpreted as Perl code, as in:

$string =~ s/(A|B)/ $1 eq 'A' ? '123' : '456' /eg;

You can even include multiple expressions, separated by semicolons, inside the substitution; the last expression's value is what will be substituted into the string. If you do this, you may find it useful to use paired delimiters for readability, like this:

$string =~ s{(A|B)}{
    my $foo = "";
    $foo = '123' if $1 eq 'A';
    $foo = '456' if $1 eq 'B';
    $foo;  # <-- this is what gets substituted for the pattern
}eg;

If your patterns are constant strings (as in the simple examples above), an even more efficient solution is to use a look-up hash, as in:

my %map = ('A' => '123', 'B' => '456');
$string =~ s/(A|B)/$map{$1}/g;

With this method, you don't even need the /e modifier (although, for this specific example, adding it would make no difference). The advantage of using /e is that it lets you implement more complicated rules for choosing the replacement than a simple hash lookup would allow.

0
Krupa Kiran On

String="A B C D"

echo $String | perl - pi - e 's/A/123/' && perl - pi - e 's/C/456/'