Bash function to assist in notification of process completion
Jan 26th, 2010 by dmess0r
Quite the messy title, but it really indicates what I am about to demonstrate.
As a sysadmin, there are countless times when I execute a long-running command, and every time I wind up making a statement to myself: “I wish I could get notified when this thing finishes.”
About a month ago I had some time on my hands and I finally figured out how to make it work using bash, procps, coreutils and mailx. Essentially any standard Linux distribution will suffice.
Before I begin, I’ve only tested this using Bash 3.2.25 and procps 3.2.7, and coreutils 5.9.7, so your mileage may vary.
To best understand to what I am referring, let’s use an example. I have a find(1) statement which is traversing an enormous filesystem. While I expect the command to take several hours, it is important that I know when it finishes.
find /export/hugearray/ -type f -iname "*.bz2" -exec rm -f {} \;
Now clearly if there are a significant number of files removed, one could make the argument that the result would be noticeable by the increase in available space on the array. While this statement could be true, it doesn’t lend itself to the countless other conditions or situations where disk space may have zero bearing on the outcome of the process.
What I wanted to do was be able to execute a command-line like the following:
command ; notifyme
Where “command” is some long-running task, and “notifyme” is a command which does what it says, notifies me. The big issue was, how could I execute a command on the same line which could step back and see when the previous command finished, and the name of that command? The obvious answer was using bash_builtins(1), “history”, but it was much easier said than done.
It is easy to craft a command that notifies you when the previous command finishes, but it is another thing to get the full argv of the previous command.
Get on with it, how did you solve this?
Alright alright.
I started with “history”. Using “history 1″ we can get the most recently executed command:
[evan@nidhoggr ~]$ ls &>/dev/null ; history 1 1022 ls &>/dev/null ; history 1
Okay, we’ve obtained the target line, but how do we ditch the history number, and pull off everything including the semi-colon on?
Let’s start by trimming off the history number. While we could use awk or sed for this, we know that there are exactly 8 bytes of padding in front of each history command. I used cut(1) to achieve this.
[evan@nidhoggr ~]$ ls &>/dev/null ; history 1 | cut -b 8- ls &>/dev/null ; history 1 | cut -b 8-
Now, how do we trim off everything from the LAST semi-colon on, and preserve the original command? It was at this point where I decided to function-ize this process as bash has very convenient string manipulation syntax.
Here is the above command-line functionized so far:
function notify { str1="`history 1 | cut -b 8-`" echo ${str1} }[evan@nidhoggr ~]$ (ls ; ls ; ls) &>/dev/null ; notify (ls ; ls ; ls) &>/dev/null ; notify
Now we need to tease off the last semi-colon and everything after it, thankfully can do this in bash, by using “%;*” appended on to our string.
function notify { str1="`history 1 | cut -b 8-`" str2="${str1%;*}" echo ${str2} }[evan@nidhoggr ~]$ (ls ; ls ; ls) &>/dev/null ; demo (ls ; ls ; ls) &>/dev/null
At this point we have the meat finished, and now all we need to do is send the output somewhere:
function notify { mail="1234567890@txt.att.net franken@berries.com" str1="`history 1 | cut -b 8-`" str2="${str1%;*}" echo ${str2} | mail -s CMD_FINISH ${mail} }
And there we have it. We define to where to send the mail, first being an SMS to Email gateway, and the other to a local address. We use “CMD_FINISH” in the subject to make the SMS and Email mind-bendingly obvious, with the finished command residing in the body.
Just append this stanza into your ~/.bashrc and you’re good to go.
# .bashrc # Source global definitions if [ -f /etc/bashrc ]; then . /etc/bashrc fi # User specific aliases and functions function notify { mail="1234567890@txt.att.net franken@berries.com" str1="`history 1 | cut -b 8-`" str2="${str1%;*}" echo ${str2} | mail -s CMD_FINISH ${mail} }
Usage is how you would expect:
[evan@nidhoggr ~]$ ls &>/dev/null ; notify [evan@nidhoggr ~]$
It is important to point out that bash aliases cannot accept arguments. In order to pass one variable to another in bash, you must use a function. We see this in the bash(1) man page.
There is no mechanism for using arguments in the replacement text. If arguments are needed, a shell function should be used ...
I hope this post serves other people well. Finally spending the time and figuring this out has really made my life easier.



