0

i want to do the following:

tail -f some file { if newline matches/contains

somestring ( perform action1; perform action2; ...; more actions)

somestring2 ( perform other actions) ...

somestring999 (perform even another action) }

This is the closest i got for now:

tail -f /mnt/zandrologs/* | awk '/GRRFIELD/ { system("echo \"test\"") }'

but this will only recognise one parameter and it will only execute one command. I will need to have it recognising multiple patterns and executing multiple commands. Eventually i put this just in some bash script

Maybe this is not just the way to continue, but this is where i got just now. now it just reads the logs until it finds a string and outputs some garbage to teh console (test). I want to extend it, but i limited the question to the essence of what I want to do.

Thanks for helping me out with some basic syntaxis.

1

You can continue with You approach if You like it that way.
You can of course execute multiple commands for each match and match different lines with different sequence of commands (and could also share some commands for different type of lines while having more specific commands running in addition).
Ex:

tail -f /mnt/zandrologs/* | awk '
/GRRFIELD/{ system("echo \"test\""); system("echo \"testG\""); }
/FRRFIELD/{ system("echo \"test2\""); system("echo \"testF\""); }
/FIELD/{ system("echo \"shared command for all lines containing FIELD\"");}'

You can also omit the newlines and could replace them by blanks if You like. Also You could do more sofisticated matching - and if You only ever match on $0 (the whole line) as in Your example, I would recommend to use awk in fixed fields witdh mode to avoid the overhead of splitting the lines into fields.

  • hi, thanks for your swift reply. Will try and come back to give my findings. Thank you. – grrfield Oct 12 '19 at 21:59
0

The perl File::Tail module is good for this kind of task. It may be available pre-packaged for your distribution (e.g. on debian and derivatives, sudo apt-get install libfile-tail-perl)

e.g.

#!/usr/bin/perl

use strict;
use File::Tail;

my $dir='/mnt/zandrologs';

# Get the list of logfiles in the target directory
my @logfiles = glob("$dir/*");

# Set up an array of File::Tail objects, one for each filename.
my @logs=();
foreach (@logfiles) {
  push(@logs,File::Tail->new(name => $_));
}

# Now watch those logs and do stuff when the script sees matching patterns
while (1) {
  my ($nfound,$timeleft,@pending)= File::Tail::select(undef,undef,undef,undef,@logs);

  if ($nfound) {
    foreach my $input (@pending) {

      # read the line of data from the current File::Tail object into $_
      $_ = $input->read;
      chomp;

      # Get the filename of the current File::Tail object.
      # This script doesn't use it, so it's commented out.
      # my $fn = $input->{'input'};

      if (m/somestring/) {
         # do stuff here
         # any perl code, including executing external programs with system().
      } elsif (m/somestring2/) {
         # do different stuff here
      } elsif (m/somestring3/) {
         # and more different stuff
      } elsif (m/somestring999/) {
         # yet more different stuff
      }
    }
  }
};

This will loop over the log files forever (or until killed). If any of the input files are rotated, File::Tail will automatically close and re-open the file (i.e. similar to tail -F).

When there is new data in one or more of the logs, the File::Tail::select() method returns:

  • $nfound - the number of File::Tail objects found with data pending (i.e. the number of elements in the @pending array).
  • $timeleft - the time remaining until the select() timed out, but this script didn't pass a timeout value to select (it just passed undef for everything except the @logs array.
  • @pending - an array of File::Tail objects with new, unread data

Each element of @pending is a File::Tail object with various methods (e.g. read() which returns the line of text pending for that object) and hash keys (e.g. {'input'} containing the filename).

See man File::Tail and perldoc -f select for details.


As written, the script will ignore any lines in the file that existed before this script started. You can change that to start by reading either the last n lines or even the entire file from the start by changing this line:

push(@logs,File::Tail->new(name => $_));

to (start by reading the last 10 lines in log files):

push(@logs,File::Tail->new(name => $_, tail => 10));

or (start by reading all log files from the beginning):

push(@logs,File::Tail->new(name => $_, tail => -1));

This is an effective but simple use of the module. See man File::Tail for more details and alternative ways to use it. The module also comes with a few good example scripts.


PS: I've used this module a lot over the years. e.g. I used to maintain my own script using File::Tail in the 1990s to call ipchains to automatically block IP addresses trying to do bad things. Then fail2ban came along and I switched to using that instead. I still use it for log-monitoring scripts to this day.

  • i am getting somewhere with this, although i'm not that familiar with perl. The only thing i am missing is the file name from which the "event" has been registered. Could you help me out on that one please. I want to send a message to a different tmux screen depending on the logfile attached to the specific "screen" or window – grrfield Oct 22 '19 at 19:29
  • Inside the foreach my $input (@pending) loop, the filename can be accessed via $input->{"input"}. – cas Oct 22 '19 at 23:40
  • i've updated my answer with a bit more explanation, and an example of how to get the current log's filename in the @pending loop. – cas Oct 23 '19 at 0:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.