This is the kind of problem where you need a loop so you can search for both patterns simultaneously.
awk '
BEGIN {
regex = "A|B"
map["A"] = "BB"
map["B"] = "AA"
}
{
str = $0
result = ""
while (match(str, regex)) {
found = substr(str, RSTART, RLENGTH)
result = result substr(str, 1, RSTART-1) map[found]
str = substr(str, RSTART+RLENGTH)
}
print result str
}
'
Of course, if Perl is available there's an equivalent oneliner:
perl -pe '
BEGIN { %map = ("A" => "BB", "B" => "AA"); }
s/(A|B)/$map{$1}/g;
'
If none of the patterns contain special characters, you can also build the regex dynamically:
perl -pe '
BEGIN {
%map = ("A" => "BB", "B" => "AA");
$regex = join "|", keys %map;
}
s/($regex)/$map{$1}/g;
'
By the way, Tcl has a builtin command for this called string map
, but it's not easy to write Tcl oneliners.
Demonstrating the effect that sorting the keys by length has:
without sorting
$ echo ABBA | perl -pe '
BEGIN {
%map = (A => "X", BB => "Y", AB => "Z");
$regex = join "|", map {quotemeta} keys %map;
print $regex, "\n";
}
s/($regex)/$map{$1}/g
'
A|AB|BB
XYX
with sorting
$ echo ABBA | perl -pe '
BEGIN {
%map = (A => "X", BB => "Y", AB => "Z");
$regex = join "|", map {quotemeta $_->[1]}
reverse sort {$a->[0] <=> $b->[0]}
map {[length, $_]}
keys %map;
print $regex, "\n";
}
s/($regex)/$map{$1}/g
'
BB|AB|A
ZBX
Benchmarking "plain" sort versus Schwartzian in perl: The code in the subroutines is lifted directly from the sort
documentation
#!perl
use Benchmark qw/ timethese cmpthese /;
# make up some key=value data
my $key='a';
for $x (1..10000) {
push @unsorted, $key++ . "=" . int(rand(32767));
}
# plain sorting: first by value then by key
sub nonSchwartzian {
my @sorted =
sort { ($b =~ /=(\d+)/)[0] <=> ($a =~ /=(\d+)/)[0] || uc($a) cmp uc($b) }
@unsorted
}
# using the Schwartzian transform
sub schwartzian {
my @sorted =
map { $_->[0] }
sort { $b->[1] <=> $a->[1] || $a->[2] cmp $b->[2] }
map { [$_, /=(\d+)/, uc($_)] }
@unsorted
}
# ensure the subs sort the same way
die "different" unless join(",", nonSchwartzian()) eq join(",", schwartzian());
# benchmark
cmpthese(
timethese(-10, {
nonSchwartzian => 'nonSchwartzian()',
schwartzian => 'schwartzian()',
})
);
running it:
$ perl benchmark.pl
Benchmark: running nonSchwartzian, schwartzian for at least 10 CPU seconds...
nonSchwartzian: 11 wallclock secs (10.43 usr + 0.05 sys = 10.48 CPU) @ 9.73/s (n=102)
schwartzian: 11 wallclock secs (10.13 usr + 0.03 sys = 10.16 CPU) @ 49.11/s (n=499)
Rate nonSchwartzian schwartzian
nonSchwartzian 9.73/s -- -80%
schwartzian 49.1/s 405% --
The code using the Schwartzian tranform is 4 times faster.
Where the comparison function is only length
of each element:
Benchmark: running nonSchwartzian, schwartzian for at least 10 CPU seconds...
nonSchwartzian: 11 wallclock secs (10.06 usr + 0.03 sys = 10.09 CPU) @ 542.52/s (n=5474)
schwartzian: 10 wallclock secs (10.21 usr + 0.02 sys = 10.23 CPU) @ 191.50/s (n=1959)
Rate schwartzian nonSchwartzian
schwartzian 191/s -- -65%
nonSchwartzian 543/s 183% --
Schwartzian is much slower with this inexpensive sort function.
Can we get past the abusive commentary now?
sed
, but for your simple case you can usey/AB/BA/;s/[AB]/&&/g