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

Code to do a basic s//ee (no particular reason to use !! delimiters instead of //, that's immaterial):

use strict; use warnings;
my $str = "AxyzB";
my $lhs = qr/xyz/;
my $rhs = "qqq";
$str =~ s!$lhs!"$rhs"!ee;
print "str=$str\n";

Result:

Use of uninitialized value in substitution iterator at /tmp/try.pl line 5.
str=AB

Question: how are substitution iterators involved here? Web searches turn up that term and warning in relation to back references, but there are no back references here, at least not specified by me. Same warning results with s!$lhs!$rhs!ee, that is, no quotes.

On the other hand, there is no warning if I use '$rhs' on the rhs, as in s!$lhs!'$rhs'!ee. (And of course there's no warning with just /e instead of /ee; in the real program, I need /ee.)

My (clearly wrong) intuition was that we would first get the string "qqq" after interpolation of $rhs, and that would eval to itself, or possibly the empty string. Running perl -e 'print eval("qqq");' prints qqq.

I tried both perl 5.24.0 and 5.32.1, same results (not that I expected any difference).

I seek your wisdom. Thanks.

  • Comment on /ee -> Use of uninitialized value in substitution iterator (without back references)

Replies are listed 'Best First'.
Re: /ee -> Use of uninitialized value in substitution iterator (without back references)
by choroba (Cardinal) on Apr 29, 2021 at 15:14 UTC
    Try running the code with
    sub qqq { return "triple q" }

    The /ee does kind of a double evaluation. First, "$rhs" is evaled, returning qqq, and this is run as a Perl expression. If qqq is not declared, the eval returns undef that you see reported in the warning.

    '$rhs', on the other hand, is evaled to $rhs which being run as a Perl expression returns qqq.

    map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
Re: /ee -> Use of uninitialized value in substitution iterator (without back references)
by haukex (Archbishop) on Apr 29, 2021 at 15:14 UTC

    As per Regexp Quote Like Operators, /ee means "Evaluate the right side as a string then eval the result." and "A /e will cause the replacement portion to be treated as a full-fledged Perl expression and evaluated right then and there. It is, however, syntax checked at compile-time. A second e modifier will cause the replacement portion to be evaled before being run as a Perl expression."

    So basically, "$rhs" becomes qqq, which when evaled causes an error: Try adding print $@ to your code and you'll see Bareword "qqq" not allowed while "strict subs" in use at (eval 1) line 1., so eval is returning undef which is causing the Use of uninitialized value in substitution iterator.

    Running perl -e 'print eval("qqq");' prints qqq.

    That's because that's not using strict - perl -wMstrict -e 'print eval("qqq"); print $@;' prints Bareword "qqq" not allowed while "strict subs" in use at (eval 1) line 1.

    And of course there's no warning with just /e instead of /ee; in the real program, I need /ee.

    Perhaps your example is a little too abstracted from the original program? Needing /ee is somewhat rare in my experience. Could you show a more representative example of why you (think you) need it, perhaps we can come up with a better solution?

      Thanks to both of you for your replies. That all makes sense. It was the "substitution iterator" that threw me off the track. I should have thought to check $@ ...

      As for why /ee. I agree it's unusual. I'd used Perl for some 30 years before I had occasion to do so, and there may well be a better way to do it. My goal is to specify regexp substitutions in an external file, but just as strings, not Perl code to be executed, for consistency with other parts of the program and also so I could track whether a given substitution was performed. For example, I need to remove braces from around strings (I'm not concerned about unmatched braces), so the data file has this line, separating lhs and rhs with || (arbitrary):

      \{(.*?)\}||$1
      

      I read the data file, split each line into the lhs and rhs, add to lists @lhs and @rhs. Then, to apply the substitutions to the input, I loop through the lists:

      $str = "whatever input";
      ...
      for (my $i = 0; $i < @$lhs; $i++) {
        my $lhs = $lhs->$i;
        my $rhs = $rhs->$i;
        eval { $str =~ s/$lhs/$rhs/eeg; }; # yet one more eval!
        $@ && die "eval(s/$lhs/$rhs/eeg) failed: $@";
        # ... more irrelevant stuff ...
      }
      

      This crazy "triple eval" was only way I found to apply the substitution with lhs and rhs stored in variables. From what we've already discussed, I guess adding single quotes will change things. Anyway, if a simpler way in general comes to mind, I'd be glad to hear about it. Thanks.

      P.S. I see now that I should also be checking $@ inside the eval, to see if there were errors from the /ee.

        If you require the $rhs to always be a Perl expression, then /ee works fine for me with these two examples. The $rhs in your example from the root node just needs to be adjusted to '"qqq"' so it's a valid Perl expression.

        use warnings; use strict; use Test::More tests=>2; { my $lhs = '\\{(.*?)\\}'; my $rhs = '$1'; my $str = '{abc}'; $str =~ s{$lhs}{$rhs}ee; is $str, 'abc'; } { my $lhs = 'xyz'; my $rhs = '"qqq"'; my $str = 'AxyzB'; $str =~ s{$lhs}{$rhs}ee; is $str, 'AqqqB'; }
        P.S. I see now that I should also be checking $@ inside the eval, to see if there were errors from the /ee.

        Note Bug in eval in pre-5.14 - if I wanted to play it really safe, I might do use warnings FATAL => 'uninitialized'; just before the s///ee to make certain errors in the eval propagate.

        (BTW, please use <code> tags to format your code)