5

Is there a way to use a variable as modifier in a substitution?

my $search = 'looking';
my $replace = '"find: $1 ="';
my $modifier = 'ee';

s/$search/$replace/$modifier;

I need to use an array of hashes to make bulk search-replace with different modifiers.

dawg
  • 98,345
  • 23
  • 131
  • 206
bem33
  • 63
  • 1
  • 4
  • 10
    Some modifiers can be supplied inside the regex as `(?modifier:pattern)` but not things which effect the whole thing like /g or /e. This is one of those "why are you doing that" moments. If you told us what this is for, we might be able to come up with a simpler way rather than brushing against camel hair (and opening a security hole). – Schwern Jul 13 '10 at 18:50
  • @Schwern: And it's most unpleasant if you open a hole in your camel – Borodin Jun 12 '15 at 08:06

5 Answers5

4

You could use eval, if you put on your safety goggles and your divide-by-zero suit.

E.g.:

use strict;
use warnings;
sub mk_re {
  my ($search, $replace, $modifier) = @_;
  $modifier ||= '';
  die "Bad modifier $modifier" unless $modifier =~ /^[msixge]*$/;
  my $sub = eval "sub { s/($search)/$replace/$modifier; }";
  die "Error making regex for [$search][$replace][$modifier]: $@" unless $sub;
  return $sub;
}

my $search = 'looking';
my $replace = '"find: $1 ="';
my $modifier = 'e';

# Sub can be stored in an array or hash
my $sub = mk_re($search, $replace, $modifier);

$_ = "abc-looking-def";
print "$_\n";
$sub->();
print "$_\n";
runrig
  • 6,486
  • 2
  • 27
  • 44
mob
  • 117,087
  • 18
  • 149
  • 283
4

While the method using eval to compile a new substitution is probably the most straightforward, you can create a substitution that is more modular:

use warnings;
use strict;

sub subst {
    my ($search, $replace, $mod) = @_;

    if (my $eval = $mod =~ s/e//g) {
        $replace = qq{'$replace'};
        $replace = "eval($replace)" for 1 .. $eval;
    } else {
        $replace = qq{"$replace"};
    }
    sub {s/(?$mod)$search/$replace/ee}
}

my $sub = subst '(abc)', 'uc $1', 'ise';

local $_ = "my Abc string";

$sub->();

print "$_\n";  # prints "my ABC string"

This is only lightly tested, and it is left as an exercise for the reader to implement other flags like g

Eric Strom
  • 39,821
  • 2
  • 80
  • 152
  • Why have an 'ee' modifier on every substitution? – runrig Jul 14 '10 at 15:57
  • @runrig => seemed to be needed to get the embedded evals working properly without evaling the actual substitution. There might be a better way, I didn't spend any time optimizing it. – Eric Strom Jul 14 '10 at 21:26
3

Hm, if I had to do it I would do like this:

use warnings;
use strict;
my @stuff = (
{
    search => "this",
    replace => "that",
    modifier => "g",
},
{
    search => "ono",
    replace => "wendy",
    modifier => "i",
}
);
$_ = "this ono boo this\n";
for my $h (@stuff) {
    if ($h->{modifier} eq 'g') {
        s/$h->{search}/$h->{replace}/g;
    } elsif ($h->{modifier} eq 'i') {
        s/$h->{search}/$h->{replace}/i;
    }
    # etc.
}
print;

There are only so many different modifiers you might want to use so I think this is easy enough.

You can use eval for this, but it's awfully messy.

  • 7
    Messy is in the eye of the beholder. I find this messier than the eval solution. – runrig Jul 13 '10 at 18:00
  • 2
    @runrig: "messy" in this case doesn't refer to what the code looks like. There are lots of difficult-to-track bugs you need to take care of if you use `eval`. –  Jul 14 '10 at 00:36
  • This is the solution I was thinking about before asking Stackoverflox. – bem33 Jul 14 '10 at 02:07
  • What's messy with this solution is the duplicated code, which is very hard to maintain, if there's 6 modifiers, it means there's 63 possible combinations (with disregard to order). if you change something in one if, you need to change it in all of them, which is terrible for code maintenance. I think it's best to generate the possibilities dynamically with a controlled eval(), one possible way is the answer i posted. – miedwar Jul 14 '10 at 07:27
  • With eval you don't have to compile the regex on every use. If these substitutions are being executed more than once in the same session, I'd probably go with eval. – runrig Jul 14 '10 at 15:46
2

Of course s/$search/$replace/ work as you expect. It is the dynamic modifiers that are not straightforward.

For the regular match modifiers of pimsx you can use Perl's Extended Patterns to modify the modifier flags on the fly as part of your pattern. These are of the form (?pimsx-imsx) to turn on / off those modifiers.

For the s// e and ee forms, you can use (?{ perl code}) documented in the same perlre section. For all of eval e or ee forms, consider the security of the resulting code!

There is no form to modify global to first match that I am aware of, so global vs first match would need to be separate statements.

dawg
  • 98,345
  • 23
  • 131
  • 206
2

Here's a combination of Kinopiko's answer and eval.

eval is used here to generate the lookup table in a controlled and maintainable fashion, and a lookup table is used to save all the if.. elsif.. elsif which are not too fun to look at.

(very lightly tested)

my @stuff = (
{
    search => "this",
    replace => "that",
    modifier => "g",
},
{
    search => "ono",
    replace => "wendy",
    modifier => "i",
}
);
$_ = "this ono boo this\n";

my @modifiers = qw{m s i x g e};

my $s_lookup = {};

foreach my $modifier (@modifiers) { 
    $s_lookup->{$modifier} =  eval " sub { s/\$_[0]/\$_[1]/$modifier } ";
}

for my $h (@stuff) {
    $s_lookup->{$h->{modifier}}->($h->{search},$h->{replace});
}

print; 

To be fully useful this needs:

  1. combinations of possible modifiers
  2. sort function on the lookup table so 'msi' combination and 'mis' combination will go to the same key.
miedwar
  • 7,248
  • 1
  • 16
  • 15
  • Or just add a modifier validation check to the other eval answer such as the one there now. – runrig Jul 14 '10 at 15:43