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' [email protected]

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

1
  • 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 for this type of error monitoring so problems external to mycommand don't prevent the email from being sent to you. Feb 17, 2017 at 16:42

3 Answers 3

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' [email protected] < $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..

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' [email protected]
fi
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='[email protected]'
MAIL_TO='[email protected]'
MAIL_SUBJECT="[$HOSTNAME] [Backup Report: Databases] %status  [$(date +%Y-%m-%d)]"
MAIL_SERVER='smtp.gmail.com:587'
MAIL_USER='[email protected]'
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 [email protected]::backup/ /mnt/hdd3/ylatis-cy/ 1>/dev/null 2>$CY_ERR
cy_status=$?
rsync -azv --chown root:root [email protected]::ylatisug/ /mnt/hdd3/ylatis-ug/ 1>/dev/null 2>$UG_ERR
ug_status=$?
rsync -azv --chown root:root [email protected]::ylatisrw/ /mnt/hdd3/ylatis-rw/ 1>/dev/null 2>$RW_ERR
rw_status=$?
rsync -azv --chown root:root [email protected]::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.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .