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

So this is a subroutine in my code greps in a log file for the instance of the string "FAILURE: " which is saved in the $FAILURE_SEARCH variable. It then sets that to $info for later use. What I would like to know is, is it possible to also get the line number where grep finds the string? I've tried using $. (shown) but I'm pretty sure that's an incorrect usage of $. Any help is much appreciated!

my(@Log) = @_; my $info = join("", grep(/^$FAILURE_SEARCH /, @Log)); #greps in + log for FAILURE: and sets $info equal to the line containing FAILURE +: if found chomp $info; $info =~s/$FAILURE_SEARCH //ie; if($info ne ""){ print "INFO: $info \n"; print "This occurred on line $.\n"; } return $info;

Replies are listed 'Best First'.
Re: Is this Possible?
by choroba (Cardinal) on Oct 06, 2015 at 15:17 UTC
    $. can only be used while still reading the file.
    open my $LOG, '<', $logfile or die $!; my %occurrences; while (my $line = <$LOG>) { if ($line =~ /^$FAILURE_SEARCH /) { chomp $line; $occurrences{$.} = $line; } } return \%occurrences

    When iterating over an array, you can use the index:

    my %occurrences; for my $index (0 .. $#log) { if ($log[$index] =~ /^$FAILURE_SEARCH /) { $occurrences{ 1 + $index } = $log[$index]; } } return \%occurrences

    Warning: untested code!

    Update: Tested, fixed errors (missing argument to chomp, missing + 1 for the array index.

    لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ
Re: Is this Possible?
by davido (Cardinal) on Oct 06, 2015 at 17:04 UTC

    $. is a special variable that yields the number of the most recently read record from a file. Typically that means the most recently read line. So if you were reading the file line-by-line, then it would be useful. It looks more like you've slurped the file into the @log array. In that case, $. will be useless for your purposes.

    grep can do what you want. Instead of grepping over @Log, grep over the indices of @Log, and if you wish, later you can map those indices back to the actual values represented at their positions within @Log.

    I started working on a solution to post for you, but quickly discovered other problems that raised questions I would need answers before I would be able to post a correct solution. For example:

    • On line 3 you are joining together all failure matches, including their newlines. Is this what you want? (It may be, but will make it harder to show a specific one later). Also, your regular expression has a trailing space at the end of the pattern. Is this desired?
    • On line 4 you are chomping $info. I think you're aware that's only going to remove the newline from the final segment in the joined string. Is that ok?
    • On line 5, your substitution regex has the 'e' modifier, but doesn't eval anything. And it does not have the 'g' modifier, yet I suspect you would want a global search through the string. However, it doesn't seem to make sense to substitute away all occurrences of the failure pattern, so I'm actually unclear why that line is even there.
    • On line 6 you are checking if $info contains anything. But because of line 5, it probably would never contain anything except for the portions of the joined string that didn't match your $FAILURE_SEARCH pattern.
    • Line 7 will print mostly useless information, since anything matching $FAILURE_SEARCH will have been stripped out of $info.
    • Line 8 won't print good information, since (as discussed earlier), $. is not relevant here. If you grepped over indices you could use the index though.
    • Line 10 is again providing bad information, since you've stripped out anything useful.

    I'll take a stab at it with a new approach:

    sub process_log { my ($log, $pattern) = @_; my @index = grep {$log->[$_] =~ m/^$FAILURE_SEARCH /} 0 .. $#$log; print "INFO: $log->[$_]This occurred on line $_\n" foreach @index; return join '', map {$log->[$_]} @index; } my $problems = process_log(\@Log, $FAILURE_SEARCH);

    (Untested)

    In my opinion, this is a better way:

    sub process_log { my ($log_fh, $pattern) = @_; my @issues; while( my $line = <$log_fh> ){ if( $line =~ m/^$pattern / ) { chomp $line; print "INFO: $line\nThis occurred on line $.\n"; push @issues, { log_entry => $line, line_num => $. }; } } return @issues; }

    Here our function takes a filehandle and a pattern. We read the file and process it one line at a time. If the line matches the failure pattern, we print a message, and push the line, and the line number as an anonymous hash into an array containing all of the issues discovered. At the end, we return the issues array so that the caller can inspect it further if needed.


    Dave

Re: Is this Possible?
by BillKSmith (Monsignor) on Oct 06, 2015 at 15:49 UTC