4

I seem to be very silly today, or I have a very weird problem. Please consider the following code:

#!/usr/bin/perl

use strict;
use warnings;
use warnings::unused;
use warnings FATAL => 'uninitialized';

my ($String, $Pattern);

$Pattern = "pattern";
$String = "pattern\n";

if (($String =~ m/^([^\ ]+).*$/s) &&
    defined($1) &&
    ($1 =~ m/^.*$Pattern$/s)) {

  print $1."\n";
}

This code produces the following warning (and then stops because of use warnings FATAL => 'uninitialized';):

Use of uninitialized value $1 in concatenation (.) or string at [...]

I really don't understand this because I am testing if $1 is defined (initialized) before using it. The culprit seems to be the last condition line (i.e. ($1 =~ m/^.*$Pattern$/s)). If I leave this away, the warning is gone.

But why? According to my understanding, that line should just test $1, but not change its value ($Pattern and the rest of that RegEx don't contain parentheses, so $1 should not be re-assigned a possible match).

Hmmm ... while thinking about it, it comes to my mind that Perl might set $1 and its colleagues in every case when a RegEx is matching, whether or not there are parentheses in the RegEx. This would explain the behavior. Is it true?

Binarus
  • 4,005
  • 3
  • 25
  • 41
  • 2
    Can you please try to create a simple test case that shows the behaviour? I don't see anything wrong with this code. – tinita Nov 29 '17 at 14:15
  • OK, I knew that this would happen ... I'll try to strip down my real code. Please give me an hour or so ... – Binarus Nov 29 '17 at 14:26
  • 1
    I now have updated the question with an MCVE. – Binarus Nov 29 '17 at 14:48
  • 3
    It's clear now. The last `$1 =~ ...` resets `$1`, so it's gone. You probably need to split the condition up and save `$1` in an extra var – tinita Nov 29 '17 at 14:55
  • 1
    @tinita Thanks and +1, but why? There are no parentheses in the second RegEx, so I would not expect `$1` to be re-assigned. – Binarus Nov 29 '17 at 14:56
  • 3
    Any matching regex will reset the `$1, ...` variables, which is a good thing, otherwise you would end up with a mixture of digit variables from previous matches... – tinita Nov 29 '17 at 15:01
  • @tinita You were the first to see the problem (although I came to the same idea nearly at the same time :-)). If you make your comment an answer, I'll accept it. – Binarus Nov 29 '17 at 15:21

3 Answers3

9

perldoc perlvar:

Contains the subpattern from the corresponding set of capturing parentheses from the last successful pattern match, not counting patterns matched in nested blocks that have been exited already.

Since the second match is the last successful pattern match, inside the if block $1 is undef.

You could do:

my ($match) = ($String =~ m/^([^\ ]+).*$/s);
if (defined $match && $match =~ m/^.*$Pattern$/s) {
    print $match."\n";
}
ysth
  • 96,171
  • 6
  • 121
  • 214
8

I'm creating an answer from my comments above:

if (($String =~ m/^([^\ ]+).*$/s) && # If matching, this will set $1
    defined($1) &&                   # Here $1 is still defined
    ($1 =~ m/^.*$Pattern$/s)         # If matching, will reset all $1, .. vars
) {

So, whenever a regex matches, it will reset all digit variables (and other regex related variables).

The only solution I see is to split the condition up and save $1 in an extra variable. Which is usually a good thing, anyway.

if ($String =~ m/^([^\ ]+).*$/s and defined $1) {
    my $match = $1;
    if ($match =~ m/^.*$Pattern$/s) { ... }
}

Note that in this case you don't even have to check for defined $1 because if this pattern matches, $1 will be always defined. It depends on the pattern.

perldoc perlvar:

$<digits> ($1, $2, ...)
        Contains the subpattern from the corresponding set of capturing
        parentheses from the last successful pattern match [...]
Gerhard
  • 22,678
  • 7
  • 27
  • 43
tinita
  • 3,987
  • 1
  • 21
  • 23
0

If you don't know what a complex statement is doing, break it into parts.

if( $String =~ m/^([^\ ]+).*$/s ){
    if( defined($1) ){
        if( $1 =~ m/^.*$Pattern$/s ){
shawnhcorey
  • 3,545
  • 1
  • 15
  • 17