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

Consider the following snippet:

use strict; use warnings; my $str = "Title: Learning Perl Author: Schwartz, Randal L. ISBN: xx +xx"; if ( $str =~ /^Title: (.*)\s+Author: (.*)\s+ISBN: (.*)/ ) { # print "<$1><$2><$3>\n"; my $author = $2; $author =~ s/\.//g; # Remove full stops (periods) print "<$1><$author><$3>"; # Line 8 }

The output I get is:

Use of uninitialized value <snip> line 8. Use of uninitialized value <snip> line 8. <><Schwartz, Randal L ><>

What has happened to $1 and $3?

According to perldoc perlre:

The scope of $<digit> extends to the end of the enclosing BLOCK or eval string, or to the next successful pattern match, whichever comes first.

which I understand to mean that $1 and $3 should still be in scope.

Interestingly, if the substitution fails (ie here if there is no "." in $author to substitute), line 8 works as expected...

Version: AS Perl 5.61 Win ME and XXXX

Please note: I'm not looking for alternative or "better" ways of parsing such a string; I can think of several. My question is only why this way doesn't work.

TIA

dave

Replies are listed 'Best First'.
Re: Modifying value of $1 clobbers $2, $3 etc?
by liz (Monsignor) on Oct 12, 2003 at 10:52 UTC
    I think you missed the importance of

    or to the next successful pattern match, whichever comes first

    $author =~ s/\.//g; # Remove full stops (periods)

    is that next successful pattern match, clobbering your $3!

    Liz

      Liz was absolutely right. The $1 .. $3 variables are reset after the second regexp.

      To fix the problem, save your $1 .. $3 variables in temporary local variables.

      use strict; use warnings; my $str = "Title: Learning Perl Author: Schwartz, Randal L. ISBN: xxx +x"; if ($str =~ /^Title: (.*)\s+Author: (.*)\s+ISBN: (.*)/) { my ($title, $author, $isbn) = ($1, $2, $3); $author =~ s/\.//g; # Remove full stops (periods) print "<$title><$author><$isbn>"; }

      Thanks. How could I have missed that? :-(

      dave

Re: Modifying value of $1 clobbers $2, $3 etc?
by bart (Canon) on Oct 12, 2003 at 11:42 UTC
    Liz already gave you the reason why it doesn't do what you want: because the s/// updates these variables.

    What she didn't tell you, is that you can fix this, by adding a block level, putting the s/// in its own block.

    #! perl -w use strict; use warnings; my $str = "Title: Learning Perl Author: Schwartz, Randal L. ISBN: xx +xx"; if ( $str =~ /^Title: (.*)\s+Author: (.*)\s+ISBN: (.*)/ ) { # print "<$1><$2><$3>\n"; my $author = $2; { $author =~ s/\.//g; # Remove full stops (periods) } print "<$1><$author><$3>"; # Line 8 }
    which yields:
    <Learning Perl ><Schwartz, Randal L ><xxxx>
    Ain't that magic.

    It's all hidden in that piece of documentation that you quoted:

    The scope of $<digit> extends to the end of the enclosing BLOCK or eval string, or to the next successful pattern match, whichever comes first.
    and here, because the s/// has its own block, the $1, $2, $3 also have their own limited scope. Outside that block, the outer values are restored. It is indeed as if there's an implicit local applied to them. So your values, the ones you want to show, got restored to the values your pattern match yielded, ready for you to print them.
      Why not split the sting right into properly named variables? IMHO it's more understandeble. Besides, it's more compact - which is generally a good thing.
      #! perl -w use strict; use warnings; my $str = "Title: Learning Perl Author: Schwartz, Randal L. ISBN: xx +xx"; if ( my ($title, $author, $isbn) = $str =~ /^Title: (.*)\s+Author: (.*)\s+ISBN: (.*)/ ) { $author =~ s/\.//g; # Remove full stops (periods) print "<$title><$author><$isbn>"; # Line 8 }
      The reason I didn't tell this, was that I was living under the wrong assumption that the scope trick didn't work with the regex variables $1 etc. ;-)

      Again, a new thing learned/corrected on Perl Monks! Thanks bart!

      Liz

Re: Modifying value of $1 clobbers $2, $3 etc?
by jonadab (Parson) on Oct 12, 2003 at 11:37 UTC

    liz has the problem nailed down exactly. The solution, of course, is simple:

    if ( $str =~ /^Title: (.*)\s+Author: (.*)\s+ISBN: (.*)/ ) { my ($title, $author, $isbn) = ($1,$2,$3); $author =~ s/\.//g; # Remove full stops print "<$title><$author><$isbn>"; # Do more stuff... }

    $;=sub{$/};@;=map{my($a,$b)=($_,$;);$;=sub{$a.$b->()}} split//,".rekcah lreP rehtona tsuJ";$\=$ ;->();print$/
Re: Modifying value of $1 clobbers $2, $3 etc?
by robartes (Priest) on Oct 12, 2003 at 10:55 UTC
    You give the answer yourself:
    The scope of $<digit> extends to the end of the enclosing BLOCK or eval string, or to the next successful pattern match, whichever comes first.
    Line 7 of your script is a successful pattern match (a substitution is also a pattern match), so the $<digit> vars go out of scope (well, they are reset to undef by the new pattern match).

    Update: removed inaccurate or even plain wrong stuff.

    CU
    Robartes-