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

Appealing to the perl monks... The following example is something I've struggled with for hours now. The original program uses a large array of regular expressions, and a matching array of text representing formatted output strings some of which may or may not want to use the results from the regex. as the example shows, $1, $2, etc don't seem to be undefined. Any ideas?
$exp="\/\^DMSC0022\\s+\\S+\\s+(\\w+)\$/"; $str="Logfile_Connection_Lost Hostname: $1"; $_="DMSC0022 X10 oswald"; if (eval $exp) { eval(print "$str\n"); }

Replies are listed 'Best First'.
Re: Regex frustration
by diotalevi (Canon) on Sep 25, 2002 at 01:21 UTC

    This strikes me as a startlingly unusual way to do something standard. I take it you mean to use $exp as a matching expression and then use variable interpolation to get $1 into that string. See, here's the thing. You're working too hard and it's loads easier to make that work correctly. Your original string is interpolating $1 in before the regular expression is even executed. The critical change is to either use non-interpolating single quotes or to escape the $1 as \$1. Your first use of eval is completely superfluous and reduces overall legibility.

    # Use a regex object so it's compiled only once $exp = qr|^DMSC0022\s+\S+\s+(\w+)$|; # Use a non-interpolating string (or escape the $1 as \$1 $str = 'Logfile_Connection_Lost Hostname: $1'; # I can only assume you are assigning to $_ # because this is a snippet from some larger code. On it's # own this is really weird. Normal code would do something # like "DMS ..." =~ $exp instead. $_ = "DMSC0022 X10 oswald"; # Force $exp to execute (don't just eval it, this is more correct) if ( $_ =~ $exp ) { # the eval() is still needed to force the source code # to generate. This is still silly. eval "print \"$str\""; }
    Myself? Try this:
    $exp = qr/\w+$/; $str = "Logfile_Connection_Lost Hostname:"; $input = "DMSC0022 X10 oswald"; ($hostname) = $input =~ m/$exp/g; print "$str$hostname" if defined $hostname;

    That's more legible and will run faster as well.

    Update: I completely rewrote the node since it sucked initially.

      Did you test this? It doesn't work on my system.

      C:\test>type fred.pl $exp = qr|^DMSC0022\s+\S+\s+(\w+)$|; $str = 'Logfile_Connection_Lost Hostname: $1'; # note that you have to + use single quotes or the $1 is interpolated as soon as you de fine $str, not when you eval() the string (which is completely superfl +uous) $_ = "DMSC0022 X10 oswald"; if ($exp) { print eval $str; # *now* get $str to interpolate } C:\test>fred Use of uninitialized value in print at C:\test\fred.pl line 6. C:\test>

        No, I'm an idiot and didn't. What I initially wrote was buggy and didn't work. Two problems: the expression wasn't being evaluated properly and the eval wasn't done correctly. The *right* way to do that eval (though an eval() isn't right in this case at all) was to do eval "print \"$str\"";.

        So my bad. I fixed the node so it's not buggy and has some actual good advice.

      You're right. the original code is a beast and the regex's are read from a db table. There are hundreds mostly unique some which just require a match, but most use $1, $2, $3, etc. My example was something simple in which I tried to convey the problem. While, your first suggestion may sound silly, I think it is the right fit in this case.

        Too bad for you then I guess. I'll just send a few commisseration thoughts over your way for when you're stuck doing code maintenance on that thing. :-(

        Update: Added a frownie

Re: Regex frustration
by BrowserUk (Patriarch) on Sep 25, 2002 at 01:36 UTC

    There is probably a better way, but this is tested and does work.

    I could get it to eval the value of $1 into $str either.

    Using qr// to put your regex into a string save much bother of escaping thing and saves you from needing to eval the regex.

    #! perl -sw use strict; my $exp= qr/^DMSC0022\s+\S+\s+(\w+)$/; my $str='Logfile_Connection_Lost Hostname: ?'; $_="DMSC0022 X10 oswald"; if ( m/$exp/) { my $bit = $1; $str =~ s/\?/$bit/; print $str; }

    Cor! Like yer ring! ... HALO dammit! ... 'Ave it yer way! Hal-lo, Mister la-de-da. ... Like yer ring!
Re: Regex frustration
by ehdonhon (Curate) on Sep 25, 2002 at 01:13 UTC
    You need to escape the string sigil in line 2.
    $str = "Logfile_Connection_Lost Hostname: \$1";
Re: Regex frustration
by Aristotle (Chancellor) on Sep 25, 2002 at 16:13 UTC
    Others have already pointed out some good ideas, but I'd like to expand on BrowserUk's point a bit more. The following is equivalent to his solution, but uses the builtin printf function to accomplish the job. (This way, you also get more control over what your output's going to look like, as a bonus.)
    my $rx = qr"^DMSC0022\s+\S+\s+(\w+)$"; my $fmt = "Logfile_Connection_Lost Hostname: %s"; $_="DMSC0022 X10 oswald"; if(my @match = /$rx) { printf "$fmt\n", @match; }
    If however you need true positional parameters, like if one of your output strings contains the parameters out of order as in Found $2 at $1, then that won't work for you. You need the following:
    my $rx = qr"^DMSC0022\s+\S+\s+(\w+)$"; my $fmt = "Logfile_Connection_Lost Hostname: %1"; $_="DMSC0022 X10 oswald"; if(my @match = /$rx/) { (my $msg = $fmt) =~ s/%\d+/$match[$1]/e; print "$msg\n"; }

    Note that you can't easily use literal % characters followed by digits in your output string formats then.

    All the solutions in this thread which use qr and whatever else to avoid eval are much cleaner, more efficient, more robust, more secure and more maintainable. If you find yourself using eval "STRING"; you are either doing something very clever or something very stupid. And using it in clever and valid ways is difficult. For a relatively ordinary task, there's always a better device.

    Makeshifts last the longest.

      Your suggestion worked great. I agree it is much cleaner. Many thanks for the imparted wisdom!