http://www.perlmonks.org?node_id=462974

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

I think there is a better way of doing this, but I couldn't think of it. I have a string where I need to rewrite each occurence of [string] with the following:

__[username]__[string]__

There can be multiple instances of [string] in a line, and I don't want to match [string] a second time once it's been rewritten.

Here is what I am doing now. I know there has got to be a better way, but this does work.

my $prefix = "__" . $self->{user} . "__"; my $suffix = "__"; my %hash; my $c =0; my @contexts = sort {length $b <=> length $a} @list; foreach my $context (@contexts) { if($var_val =~s/($context?)/\@\@$c\@\@/g) { $hash{$c} = "$prefix$1$suffix"; $c++; } } foreach my $key (keys %hash) { $var_val =~s/\@\@$key\@\@/$hash{$key}/g; }

Replies are listed 'Best First'.
Re: Regex help
by Roy Johnson (Monsignor) on Jun 02, 2005 at 17:45 UTC
    So you've got multiple possible strings, and some of them might be substrings of others of them? If you combine them into one alternation, longest first, you should be ok:
    my $super_regex = join '|', sort {length $b <=> length $a} map quoteme +ta($_), @list; $var_val =~ s/($super_regex)/$prefix$1$suffix/g;
    Regexp::Optimizer might be of use if @list is long.

    Update: added quotemeta as per ikegami's note.


    Caution: Contents may have been coded under pressure.
      Ah that's exactly what I was looking for. Sorry for not explaining the issue correctly in my first post. Some of the strings being substrings of others was the real issue as you guessed.
Re: Regex help
by ikegami (Patriarch) on Jun 02, 2005 at 17:41 UTC

    s///g won't search replaced text, as shown in the following snippet:

    $search_for = '[string]'; $user = '[user]'; local $_ = 'foo [string] bar [string] baz'; s/(\Q$search_for\E)/__${user}__${1}__/g; print("$_\n"); # foo __[user]__[string]__ bar __[user]__[string]__ baz

    You can use s///ge if you need to generate $user dynamically for each match.

      Silly me, I forgot what the problem was and mistated it. The problem is that the string I am searching for can exist as a substring of another element of @contexts, but I don't want it to match if it's already been rewritten.

      For example in the list I am looping through, one element could be demo-outgoing, and a later element could be demo. I don't want the string demo in the already formatted string __user__demo-outgoing__ to be matched and rewritten again.

      Does that make sense?

        So @context contains search strings? (This is why it's important to give a runable piece of code, including sample input. It takes the guessing out of the game.)

        my @contexts = qw( demo-outgoing demo ); local $_ = 'foo demo-outgoing bar'; my $regexp = join('|', map { quotemeta($_) } @contexts); s/($regexp)/__user__${1}__/g; print("$_\n"); # foo __user__demo-outgoing__ bar
Re: Regex help
by Joost (Canon) on Jun 02, 2005 at 17:44 UTC
    what's in @list? Why does your code match "@@something@@" while your explenation states "[something]"?

    how about this, assuming @strings is an array containing a list of "somethings" to replace:

    for my $string (@strings) { $input =~ s/\[\Q$string\E\]/__[username]__[$string]__/; }
    This will replace every first occurence of every string in @strings with "__[username]__[$string]__"

    update: see perlretut

Re: Regex help
by Jasper (Chaplain) on Jun 02, 2005 at 17:50 UTC
    How not to ask a question? I don't understand one thing you're trying to do.