Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl: the Markov chain saw

one liner question

by natxo (Scribe)
on Apr 20, 2017 at 09:13 UTC ( #1188377=perlquestion: print w/replies, xml ) Need Help??
natxo has asked for the wisdom of the Perl Monks concerning the following question:

I need to get the first two fields of a log file containing a certain string starting from the end of the file. Using this I get the whole last line line containing the string:
$ perl -ne '$l=$_ if /received/; END{print $l}' /var/log/rsync.log 2017/04/20 10:50:24 [26050] sent 25 bytes received 326 bytes total s +ize 52165551
How could I get the date fields (2017/04/20 10:50:24)? Right now I have this:
perl -ne '$l=$_ if /received/; END{print $l}; ($date) = $1 =~ m/^(\d+ +\/\d+\/\/d+) .*/g ; print $date, " \n" ' /var/log/rsync.log
Which should at least get me '2017/04/20' (just starting with the regex), but I get nothing back in $date, just the original line. Is it possible to achieve in a oneliner, or do I need to write a proper script? TIA.

EDIT: Thanks a lot for your solutions. The autosplit switch is really handy.

Replies are listed 'Best First'.
Re: oneliner question
by Discipulus (Monsignor) on Apr 20, 2017 at 09:19 UTC
    Hello natxo,

    to get first two fields of the splitted string you can profit of autosplit implemented by the -a switch. it fills the special @F array see perlrun

    perl -lane 'print "$F[0] $F[1]" if /received/' /var/log/rsync.log


    There are no rules, there are no thumbs..
    Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.
Re: one liner question
by Eily (Parson) on Apr 20, 2017 at 09:20 UTC

    The binding operator =~ means that the operation on the right side is applied on the variable on its left side. So your regex is applied to $1, which is empty. Try ($date,) = /^(\d+\/\d+\/\/d+)/; instead.

    Maybe you can have a look at the -a switch in perlrun. It splits the input into @F, so your first two fields are in $F[0] and $F[1] (because by default the field separator is ' ').

Re: one liner question
by haukex (Monsignor) on Apr 20, 2017 at 09:51 UTC
    $ tac /var/log/rsync.log | perl -lane 'if(/received/){ print "$F[0] $F +[1]"; exit }' # Update 2017-04-21: compacting that a little bit: $ tac /var/log/rsync.log | perl -lane 'if(/received/){print"@F[0,1]";e +xit}'

    Script (using File::ReadBackwards):

    use warnings; use strict; use File::ReadBackwards; die "Usage: $0 LOGFILE\n" unless @ARGV==1; my $FILE = shift @ARGV; my $bw = File::ReadBackwards->new($FILE) or die "Can't read $FILE: $!" ; LINE: while(defined( my $line = $bw->readline )) { chomp($line); if ( $line =~ /received/ ) { my ($dt) = $line =~ /^([\d\/]+\s+[\d\:]+)\b/ or die "Failed to parse date from: $line\n"; print "$dt\n"; last LINE; } } $bw->close;

    Update: Credit to Discipulus for the inspiration of the first one-liner. Here's another one that scans the file forwards, like your original code does, but of course it'll be less efficient on larger files. For an explanation of }{ see the "Eskimo greeting" in perlsecret.

    $ perl -lane '/received/ and $a="$F[0] $F[1]"}{print $a' /var/log/rsyn +c.log
Re: one liner question
by Anonymous Monk on Apr 20, 2017 at 17:37 UTC

    You assigned your line to $l (lowercased "L", a terrible variable name) but then tried to match against $1 (lowercased "!", a regex capture variable).

    For the regex itself:

    m/^(\d+\/\d+\/\/d+) .*/g

    Using alternate delimiters reduces escaping, making the error obvious:

    m!^(\d+/\d+//d+) .*!g

    Also, the /g can be omitted.

      That's actually an excellent catch.

      In my defence I will state that the putty I have to use at work does not have the inconsolata font I normally use to prevent me from making those mistakes so often.

Re: one liner question
by Lotus1 (Curate) on Apr 21, 2017 at 14:01 UTC

    I'll put in my two cents worth. Use Tie::File to get an array and then reverse it before traversing it.

    use warnings; use strict; use Tie::File; tie my @filearray, 'Tie::File', 'backwards.log' or die "couldn't tie f +ile $!\n"; foreach (reverse @filearray) { if (/received/) { printf "%s %s\n", (split)[0,1]; last; } }

      Tie::File is a good idea. One disadvantage is that it'll have to scan the entire file once and use up some memory to store the offsets of all the lines in the file when it is determining the size of the array. I wondered if the optimization of for (reverse @array) (since Perl v5.8.6) also applies to tied arrays, and it turns out that it does:

      { package LoggedArray; use Tie::Array; our @ISA = ('Tie::StdArray'); sub TIEARRAY { bless [@_[1..$#_]], $_[0] } for my $sub (qw/ FETCHSIZE STORESIZE STORE FETCH CLEAR POP PUSH SHIFT UNSHIFT EXISTS DELETE SPLICE EXTEND /) { no strict 'refs'; *$sub = sub { my $self = shift; warn "$sub @_\n"; $self->${\"SUPER::$sub"}(@_); } } } tie my @array, 'LoggedArray', qw/one two three/; print "<$_>\n" for reverse @array; __END__ FETCHSIZE FETCH 2 <three> FETCH 1 <two> FETCH 0 <one>

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1188377]
Approved by Discipulus
Front-paged by Corion
[holli]: i'm learning but still full of perl5isms :)

How do I use this? | Other CB clients
Other Users?
Others making s'mores by the fire in the courtyard of the Monastery: (4)
As of 2017-09-23 13:00 GMT
Find Nodes?
    Voting Booth?
    During the recent solar eclipse, I:

    Results (272 votes). Check out past polls.