3

Can I make cause cron to only send the email if the output (stderr) contains a certain string?

I'm aware of this answer but the command I run doesn't distinguish between stdout/stderr, it always just outputs to stdout, so I need to look for a string.

So far I got this and it basically works EXCEPT grep doesn't pass on the output to the mail command, so I just get an empty email:

0 5 * * * root mycommand | grep -q 'Renewal was done' && mail -s 'Renewal completed' my@email.com

How can I get the entire output from mycommand included in the email?

TheStoryCoder
  • 254
  • 3
  • 13
  • If a successful renewal is the normal/default case, I'd recommend you send yourself notifications for the exception/error case. It's easy to automatically ignore a regular email after a while and then not notice when something actually goes wrong. Furthermore, I'd suggest to use a third-party service such as [WDT.io](https://WDT.io) for this type of error monitoring so problems external to `mycommand` don't prevent the email from being sent to you. – Christian Pekeler Feb 17 '17 at 16:42

3 Answers3

2

I would highly suggest you put the logic into a script and run that script in cron rather than try and make a one liner script in cron. That way you can easily test it outside of cron, eg:

    #!/bin/bash
    tmp=/tmp/t$$
    mycommand > $tmp 
    if grep -q 'Renewal was done' $tmp
    then
        mail -s 'Renewal completed' my@email.com < $tmp
    fi

    rm -f $tmp
    exit 0

You can add checking of mycommand exit status, pass the command being run, the string being matched and the email address in as parameters etc..

Loner
  • 3
  • 2
iheggie
  • 146
  • 3
1

This might work.

#!/bin/sh
COMMAND=`mycommand`
FINDSTR="renewal was done"

ANSWER = `$COMMAND | grep $FINDSTR` 

if $ANSWER; then
  echo $ANSWER > mail -s 'Renewal Completed' my@email.com
fi
Jeff W.
  • 511
  • 2
  • 7
0

Here's how I do it:

In this scenario I have 4 databases (dumps) I need to backup and if everything went normal I want to receive an email mentioning so. If I had errors I want a message with a different subject to point it out, the failed database included in the message body and the error log attached to the email.

The first file (/root/templates/ylatis-backup.txt) is just a message body template:

Backup status report from: %hostname

Network configuration:
%net_config

Database backups:
ylatis-cy :%cy_status
ylatis-ug :%ug_status
ylatis-rw :%rw_status
ylatis-lc :%lc_status

All words prefixed by % are treated as variables and replaced by sed later on in the script.
%hostname and %net_config are used to identify which computer the email is coming from.
The variables ending in _status are replaced with the word NORMAL or ERROR so I can know what went wrong.

#!/bin/bash

NET_CONFIG=$(ifconfig |grep inet |grep -v inet6 |grep -v '127.0.0.1')
MESSAGE_TEMPLATE=/root/templates/ylatis-backup.txt
REPORT_FILE=/tmp/bkup-report-$$.txt
MESSAGE=$(cat $MESSAGE_TEMPLATE)

MAIL_FROM='no-reply@hostname.com'
MAIL_TO='user@hostname.com'
MAIL_SUBJECT="[$HOSTNAME] [Backup Report: Databases] %status  [$(date +%Y-%m-%d)]"
MAIL_SERVER='smtp.gmail.com:587'
MAIL_USER='no-reply@hostname.com'
MAIL_PASS='**********'

CY_ERR=/tmp/ylatis-cy_err.$$
UG_ERR=/tmp/ylatis-ug_err.$$
RW_ERR=/tmp/ylatis-rw_err.$$
LC_ERR=/tmp/ylatis-lc_err.$$

ERROR_LOG=/tmp/error_log-$$.txt

MESSAGE=$(echo "$MESSAGE" |sed 's@%hostname@'"$HOSTNAME"'@g')
MESSAGE=$(echo "$MESSAGE" |sed 's@%net_config@'"$NET_CONFIG"'@g')

mount -a
rsync -azv --chown root:root lp@10.10.10.14::backup/ /mnt/hdd3/ylatis-cy/ 1>/dev/null 2>$CY_ERR
cy_status=$?
rsync -azv --chown root:root lp@10.10.10.10::ylatisug/ /mnt/hdd3/ylatis-ug/ 1>/dev/null 2>$UG_ERR
ug_status=$?
rsync -azv --chown root:root lp@10.10.10.10::ylatisrw/ /mnt/hdd3/ylatis-rw/ 1>/dev/null 2>$RW_ERR
rw_status=$?
rsync -azv --chown root:root lp@10.10.10.10::labco/ /mnt/hdd3/ylatis-labco/ 1>/dev/null 2>$LC_ERR
lc_status=$?

if [ $cy_status -eq 0 ] && [ $ug_status -eq 0 ] && [ $rw_status -eq 0 ] && [ $lc_status -eq 0 ]; then
##########
#ALL GOOD#
##########

MAIL_SUBJECT=$(echo "$MAIL_SUBJECT" |sed 's@%status@[NORMAL]@g')

MESSAGE=$(echo "$MESSAGE" |sed 's@%cy_status@NORMAL@g')
MESSAGE=$(echo "$MESSAGE" |sed 's@%ug_status@NORMAL@g')
MESSAGE=$(echo "$MESSAGE" |sed 's@%rw_status@NORMAL@g')
MESSAGE=$(echo "$MESSAGE" |sed 's@%lc_status@NORMAL@g')

echo "$MESSAGE" >$REPORT_FILE
sendemail -f $MAIL_FROM -t $MAIL_TO -u $MAIL_SUBJECT -o message-      file=$REPORT_FILE -s $MAIL_SERVER -xu $MAIL_USER -xp $MAIL_PASS

else
################
#ERRORS OCCURED#
################

MAIL_SUBJECT=$(echo "$MAIL_SUBJECT" |sed 's@%status@[ERROR]@g')

if [ $cy_status -eq 0 ]; then
  MESSAGE=$(echo "$MESSAGE" |sed 's@%cy_status@NORMAL@g')
else
  MESSAGE=$(echo "$MESSAGE" |sed 's@%cy_status@ERROR@g')
fi

if [ $ug_status -eq 0 ]; then
  MESSAGE=$(echo "$MESSAGE" |sed 's@%ug_status@NORMAL@g')
else
  MESSAGE=$(echo "$MESSAGE" |sed 's@%ug_status@ERROR@g')
fi

if [ $rw_status -eq 0 ]; then
  MESSAGE=$(echo "$MESSAGE" |sed 's@%rw_status@NORMAL@g')
else
  MESSAGE=$(echo "$MESSAGE" |sed 's@%rw_status@ERROR@g')
fi

if [ $lc_status -eq 0 ]; then
  MESSAGE=$(echo "$MESSAGE" |sed 's@%lc_status@NORMAL@g')
else
  MESSAGE=$(echo "$MESSAGE" |sed 's@%lc_status@ERROR@g')
fi

  echo "$MESSAGE" >$REPORT_FILE

  cat /dev/null >$ERROR_LOG
  find /tmp/ -name \*.$$ -exec cat {} \; >>$ERROR_LOG
  cp $ERROR_LOG .
  sendemail -f $MAIL_FROM -t $MAIL_TO -u $MAIL_SUBJECT -o message-file=$REPORT_FILE -a error_log-$$.txt -s $MAIL_SERVER -xu $MAIL_USER -xp  $MAIL_PASS
  rm error_log-$$.txt

fi

#Cleanup
rm /tmp/*.$$ 2>/dev/null
rm /tmp/*$$.txt 2>/dev/null

Note how I am monitoring the exit status of rsync to determine what the message and subject of my email should be. In my case I am discarding normal output (1>/dev/null) and keeping only the standard error output (2>$error_log). You could just use the >> operator if you want to keep all the output. You could also adjust to include the log in the message instead of an attachment for convenience if you are reading your mail from terminal.

Maybe it looks like an overkill to a relatively simple question but I think it outlines well what you are trying to do. You need to build your message while running your command through an intermediate script and send it later. If you find the above code useful and have any questions on it feel free to ask in comments.