Perl: replacement pattern with different sized drawing
here is my string A B C D
and i want to replace A
with 123
and C
with 456
eg. However, this doesn't work.
$string=~ s/A|B|C|D/123|B|456|D/;
I need this 123 B 456 D
one but I get this123|B|456|D B C D
Perhaps because the number of characters in my two templates is different.
Is there a way to replace templates of different sizes with some other piece of code? Many thanks.
source to share
Something like this using eval (untested).
$string=~ s/(A)|C/ length($1) ? '123': '456'/eg;
Using the eval flag on a form s///
means evaluating the replacement as a line of code that returns a value.
In this case, it executes the ternary conditional replacement code.
It's like a built-in regex callback.
It is much more difficult as it can be s///eeg
, so it is better to refer to the docs.
Remember that eval is really evil, with a bug!
source to share
You get what you expect from you. Your regular expression looks for one of the following values: 'A'
or 'B'
or 'C'
or 'D'
and replaces it with a literal string '123|B|456|D'
. Therefore, 'A B C D'
→'123|B|456|D B C D'
Thus, it finds the first occurrence, 'A'
and replaces it with the string you specified. The variable matches different strings, but the pipe symbols mean nothing in the replacement slot.
What you need to do is create a mapping from input to output, for example:
my %map = ( A => '123', C => '456' );
Then you need to use it in replacements. Let's find the expression to search for:
my $search = join( '|', keys %map );
Now write the substitution (I prefer curly braces when I replace code that has code in it:
$string =~ s{($search)}{ $map{$1} }g;
The switch g
means we match every part of the string we can, and the switch e
tells Perl to evaluate the replacement expression as Perl code.
Output signal '123 B 456 D'
source to share
The easiest way to do this is with two replacements:
$string =~ s/A/123/g;
$string =~ s/B/456/g;
or even (using an inline for
shorthand to apply multiple substitutions to a single line):
s/A/123/g, s/B/456/g for $string;
Of course, for more complex patterns, this may not give the same results as both substitutions in one pass; in particular, this can happen if patterns can overlap (as in = YZ
, B = XY
), or if pattern B can match a string replaced by pattern A.
If you want to do this in one pass, the most common solution is to use a modifier /e
, which forces the replacement 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 a wildcard; the last value of the expression is what will be replaced with a string. If you do this, you can usefully use paired read delimiters , for example:
$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 a modifier /e
(although for this particular example adding one doesn't matter). The advantage of using it /e
is that it allows you to implement more complex rules for choosing a replacement than a simple hash lookup would allow.
source to share