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

I run this script:

use strict; use warnings; my @lines = ( "Once upon a time", "scrooge & donald", "went for a long walk" ); my @list = ("huey", "dewey", "louis"); foreach my $who (@list) { my $sub = "\$ln =~ s/scrooge (& donald)/$who \$1/g;"; print "$who\n"; foreach my $ln (@lines) { eval $sub; print "$ln\n"; } print "\n"; }

and I get this:

huey Once upon a time huey & donald went for a long walk dewey Once upon a time huey & donald went for a long walk louis Once upon a time huey & donald went for a long walk

not what I was expecting. Anyone help?

--
TTFN, FNORD

xaphod

Replies are listed 'Best First'.
Re: variables in substition/eval
by kcott (Archbishop) on Aug 18, 2017 at 06:33 UTC

    G'day xaphod,

    [Assumption: As already stated, you need to tell us what you do expect, not just that you didn't get what you expected. I'm assuming that, in the three blocks of output, you want "huey & donald", "dewey & donald" and "louis & donald", respectively.]

    AM has already pointed out the issue. In Perl v5.14, non-destructive substitution was introduced, which could get around this problem (see "perl5140delta: Non-destructive substitution" for details and additional information).

    It looks like you've probably over-engineered your solution and, as result, generated the unexpected output. Your nested loops could have been written like the following: I've commented out all the over-engineered code so you can see how much simpler it could have been.

    foreach my $who (@list) { #my $sub = "\$ln =~ s/scrooge (& donald)/$who \$1/g;"; print "$who\n"; foreach my $ln (@lines) { #eval $sub; #print "$ln\n"; print $ln =~ s/scrooge (& donald)/$who $1/r, "\n"; } print "\n"; }

    That produces the output as per my "Assumption".

    If your version of Perl is older than 5.14, which is now over six years old[perlhist], consider upgrading but, for now, you'll need to code this the old way by assigning the original text to a new variable first, then performing the substitution on that new variable, leaving the original unchanged.

    By the way, in your original regex, you used the 'g' modifier. This is completely unnecessary: it's for performing multiple matches in a single regex; not for single matches that happen to be performed on multiple occasions. These three one-liners should show the difference:

    $ perl -E 'my $x = "AAA"; $x =~ s/A/B/; say $x' BAA $ perl -E 'my $x = "AAA"; $x =~ s/A/B/ for 0 .. length($x) - 1; say $x +' BBB $ perl -E 'my $x = "AAA"; $x =~ s/A/B/g; say $x' BBB

    That's just a guess as to why you thought you needed a 'g' modifier. See "perlre: Modifiers" for details.

    — Ken

      In a situation like this, my own preference is to use look-arounds to avoid capturing (and substituting back) more than necessary (tested under 5.8.9):

      c:\@Work\Perl\monks>perl -wMstrict -le "my @lines = ( 'Once upon a time', 'scrooge & donald', 'went for a long walk', ); ;; my @list = qw(huey dewey louis); ;; foreach my $who (@list) { print qq{who '$who'}; foreach my $ln (@lines) { (my $newln = $ln) =~ s{ scrooge (?= \s+ & \s+ donald) }{$who}xmsg +; print qq{ '$newln'}; } } " who 'huey' 'Once upon a time' 'huey & donald' 'went for a long walk' who 'dewey' 'Once upon a time' 'dewey & donald' 'went for a long walk' who 'louis' 'Once upon a time' 'louis & donald' 'went for a long walk'


      Give a man a fish:  <%-{-{-{-<

        My intention was to show the use of the 'r' modifier to resolve the problem. Because 'r' may have appeared to replace 'g', I commented on that also.

        Not commenting on other aspects of the code should not be construed as tacit approval. I still don't know what output was expected; I don't know the context of this code; and there are other unknowns, such as how "scrooge" relates to the anything here.

        There are in fact all sorts of issues with the code, ranging from poorly named variables (e.g. @list) to bad grammar (e.g. non-capitalised proper nouns).

        I possibly would have used an easily identifiable placeholder such that the regex required no captures or lookaround assertions. The substitution may simply have been something like:

        ... s/__NEPHEW__/$nephew/r ...

        Update: Did Donald Duck have a relative named Scrooge? Perhaps that explains the "scrooge" connection. Still just guessing. :-)

        — Ken

Re: variables in substition/eval
by Marshall (Canon) on Aug 18, 2017 at 01:40 UTC
    Please show us what you want!
    Your code "works" as far as it goes. My code below is just a complete guess.
    I have no idea of what you want to accomplish.
    use strict; use warnings; my @lines = ( "Once upon a time", "scrooge & donald", "went for a long walk" ); my $lines = join("\n", @lines) ."\n\n"; my @list = ("huey", "dewey", "louis"); foreach my $who (@list) { my $new_lines = $lines; $new_lines =~ s/scrooge/$who/; print $new_lines; } __END__ Once upon a time huey & donald went for a long walk Once upon a time dewey & donald went for a long walk Once upon a time louis & donald went for a long walk
    Ok,here is another guess:
    use strict; use warnings; my @lines = ( "Once upon a time", "scrooge & donald", "went for a long walk" ); my $lines = join("\n", @lines) ."\n\n"; my @list = ("huey", "dewey", "louis"); foreach my $who (@list) { my $new_lines = $lines; $new_lines =~ s/(&\s*donald)/& $who/; print $new_lines; } __END__ Once upon a time scrooge & huey went for a long walk Once upon a time scrooge & dewey went for a long walk Once upon a time scrooge & louis went for a long walk
Re: variables in substition/eval
by AnomalousMonk (Archbishop) on Aug 18, 2017 at 03:29 UTC

    And if you have Perl version 5.10 or greater, here's a variation that uses the  \K variable-width positive look-behind operator:

    c:\@Work\Perl\monks>perl -wMstrict -le "use 5.010; ;; my @lines = ( 'Once upon a time', 'scrooge & donald', 'went for a long walk', ); ;; my @list = qw(huey dewey louis); ;; foreach my $who (@list) { print qq{who '$who'}; foreach my $ln (@lines) { (my $newln = $ln) =~ s{ scrooge \s+ & \s+ \K donald }{$who}xmsg; print qq{ '$newln'}; } } " who 'huey' 'Once upon a time' 'scrooge & huey' 'went for a long walk' who 'dewey' 'Once upon a time' 'scrooge & dewey' 'went for a long walk' who 'louis' 'Once upon a time' 'scrooge & louis' 'went for a long walk'
    See  \K in Extended Patterns section of perlre. (Could also use a standard look-ahead to achieve the original  scrooge (& donald) match.)


    Give a man a fish:  <%-{-{-{-<

Re: variables in substition/eval
by Anonymous Monk on Aug 17, 2017 at 22:54 UTC
    $ln is an alias to the items of the list, so the first time thru the loop it modifies @lines, so it no longer says "scrooge" and the regex doesn't match anymore. Try dumping @lines to see and assign $ln to a new var and modify that.
Re: variables in substition/eval
by stevieb (Canon) on Aug 17, 2017 at 22:46 UTC

    Well, for starters, it would be really helpful if you'd edit your question and update it with the text you *do* expect as output.