connollyc4 connollyc4 - 6 months ago 13
Bash Question

.sh script if number > 100 send email with the number



I am trying to write a script that looks at a .txt file and determins if the first number is greater or equal to 100. If it is then send an email, if it is not, do nothing.

Here is an example of what the .txt file would look like

vi output.txt

108


since that number is over 100 it should send an email of the number:

Example of email

You have 108 errors


This is what I got so far:

#!/bin/bash

filename = '/home/tted01/SPOOL/error.txt'

while read -r line
do
id = $(cut -c-3 <<< "$line")
echo $id >> /home/tted01/SPOOL/output.txt
done < "$filename"

mailx -s "ERRORS" ted.neal@gmail.com -- -f ted.neal@gmail.com < /home/tted01/SPOOL/output.txt
sleep 5

exit 0


I can't figure out the conditional statement and how to parse the variable.

Answer

Update Changed the mailx invocation to send a message and attach the file.


Here is a simple Perl script. Since we need to both send a message and a file, the example from the question is changed to attach the file. This seems suitable with a small text file, which contents is discussed in the body anyway. There are other ways to do this with mailx, see man mailx on your system.

use warnings;
use strict;

my $file = '/home/tted01/SPOOL/error.txt';
my $mark = 100;

# Slurp the whole file into a variable
my $cont = do {
    open my $fh, '<', $file  or die "Can't open $file -- $!";
    local $/; 
    <$fh>;
};

# Pull the first number
my ($num) = $cont =~ /^(\d+)/;

if ($num > $mark) 
{
    my $body = "You have $num errors.";
    my $cmd_email = "echo $body | " .
        "mailx -a $file -s \"ERROR\" ted.neal\@gmail.com";
    system($cmd_email) == 0  or die "Error sending email -- $!";
}

Since this is clearly a small enough file it is read in one go, into a variable. We extract the very first number that was in the file by a simple regex. (A "number" is understood as consecutive digits, up to anything other than a digit.)

The email command is executed via system, which in Perl uses sh. Perl's system returns a 0 if a command was executed without errors so we test against that. Note that this doesn't tell us how the command itself did, only whether it ran fine. All this can be done in other ways, if needed -- one can use backticks (qx) to get back the output, with possible stream redirection in the command to get the error as well. Also, bash can be invoked explicitly if needed.


Here is another option for how to compose the message. If the file is indeed so small, you can include its contents in the message body.

Everything before and after this snippet stays as above.

my ($num) = $cont =~ /^(\d+)/;

chomp($cont);
my $body = "You have $num errors. Log file: $file. Contents: $cont";

my $cmd_email = "echo $body | mailx -s \"ERROR\" dimcoviz\@nacse.org";

Note that here we have to be careful with newlines, thus the chomp and no newlines in the message body. So this is pushing it a little.

If the log file may or may not be too large, you can check the number of lines and perhaps include the first 5 or 10 (and attach the file), or include the whole file as above (if small enough). This is easy to process in Perl. You can refine this further a lot. Finally, if things grow and get sophisticated you can use Perl modules for email.


A very reasonable question came up -- what if the number is not the very first thing in the file? What if it is the first thing on the second line, for example?

If the file is small one can still read it into a variable like above, and use a (more complicated) regex to find the pattern. For example, if the number is the first thing on the second line

my ($num) = $cont =~ /.*\n(\d+)/;

The pattern matches the newline directly by \n and captures the first number after it. Since there is no /s or such modifiers, a newline is not matched by . so the greedy .* does stop at the first newline, as needed.

However, this gets nastier very rapidly as more complicated requirements come. So better just process line by line.

# Instead of reading all content into $cont
open my $fh, '<', $file  or die "Can't open $file -- $!";

my $num;
while (my $line = <$fh>) 
{
    if ($. == 2) {                    # $. is number of lines read
        ($num) = $line =~ /^(\d+)/;
        last;                         # we are done, quit the loop
    }
}
# Process the same way as above

We use one of Perl's built-in special variables, $., which holds the number of lines having been being read from the (last accessed) filehandle. See Special Variables in perlvar. Note that the line is "read" at <>, so inside the loop $. is 1 when we are processing the first line, etc. The regex is the same, and here is an explanation that is missing above.

The match $line =~ m/.../ always returns a list of matches since it can match many times, so we 'catch' it in a list, using (). Since there is clearly just one here it is enough to use one variable, ($num). The m/ may be omitted and you'll generally see it that way, =~ /.../.

This is a simple condition, that it's the second line. But this way you can use far more sophisticated ones as well.