Welcome to the second edition of automation and having fun in text-mode only. We will build a small monitoring system and integrate it with a shell script. Because POSIX sh is boring and the script doesn’t need to work on any limited platform, we will use Z shell

So, the first line of the script is:

#!/bin/zsh

next, we call the program whose output should be monitored. The idea is that we want to get informed whenever there is a change in the output. Of course, you can fill in whatever criterion suits your needs here. The output is then stored to a temporary file:

perl googl.pl > googl-result.$$

the resulting output is compared to the output from last time the program was run, and if there is a change, notification should happen. the cmp program is used to check if two files are equal. additionally, we figure notification should only get sent when there actually is an output. a copy of the message should be logged to the terminal. so, the following code is crafted

if { ! cmp -s googl.output googl-result.$$ } {
    if [[ -s googl-result.$$ ]] {
        date
        cat googl-result.$$
        perl send-notification.pl "$(<googl-result.$$)"
    }
}

note that so far this is a place-holder, nothing has been thought up as how to deliver the notification. That’s the next topic

to finish off the script, we should store the newly recorded output as last output. Then, we wait a while before restarting the script for the next iteration

mv googl-result.$$ googl.output
sleep 60
exec $0

(first part: plugwork in textmode)

for this topic, the choice has been made to send notification via SMS. Short message service is delivering short messages to mobile phones, so the important notification will reach me even when I’m not on the computer. So how can the SMS be sent? I have signed up with a web service where I can charge money, and then send messages by filling in a web form. Sounds familiar? Of course this is another job for WWW::Mechanize!

Here’s the start of our SMS script:

use strict; use warnings;
use open qw(:std :utf8);
use utf8;
use LWP::ConnCache;
use WWW::Mechanize;

next we identify the service to use, in this example it will be nonoh (unfortunately I’m not making any money by mentioning them). When we go to their web site and log in, we notice that SMS can be sent under the /web_sms URL. However, if we try mech-dump https://www.nonoh.net/web_sms then the result won’t be what we want. A log-in is necessary first. So let us log in using Mechanize:

my $user = 'MY_USERNAME_HERE';
my $pass = 'MY_PASSWORD_HERE';
my $base = 'https://www.nonoh.net';

my $mech = new WWW::Mechanize (autocheck => 1, stack_depth => 0);
$mech->conn_cache(new LWP::ConnCache ());

$mech->get("$base/login/");
$mech->submit_form(
    with_fields => {
        'login[username]' => $user,
        'login[password]' => $pass,
    });

(we took the form info from mech-dump again)

then, we can successfully navigate to the SMS form page but we need to dump the forms via script now:

$mech->get("$base/web_sms/");
$mech->dump_forms

notice that dump_forms call there? if we run this script now, then it will output in the same way as mech-dump:

POST https://www.nonoh.net/web_sms [sendsms_form]
  message=                       (textarea)
  callerid=                      (option)   [*(callerid)]
  phonenumber=                   (textarea)
  <NONAME>=Add contacts          (submit)
  sending_options=send_now       (radio)    [*send_now|send_later]
  sending_day=15                 (text)
  sending_month=06 2012          (option)   [*06 2012/June 2012|07 2012/July 2012|...]
...

now we’re almost there. we write in the script:

my $msg = shift;
my $from = 'MY_CALLERID';
my $to = '+34123456789';
$mech->submit_form(
    with_fields => {
        message => $msg,
        callerid => $from,
        phonenumber => $to,
    });

(valid caller-ids can be seen in the dump output)

again, the resulting web page can be stored away with the $mech->content method. after examining the resulting document we find we would like to see the error/success messages printed to the console, and we can identify that HTML classes are used to mark such results in the web page (possibly by using the Firefox inspector). So, we can extend the script with that information (we use our old friend LibXML):

use XML::LibXML;

my $lx = new XML::LibXML ();
$lx->recover(2);
my $doc = $lx->parse_html_string($mech->content, { suppress_errors => 1 });

for ($doc->findnodes('//span[contains(@class, "error")]')) {
    print '>>', $_->textContent, "\n"
}

for ($doc->findnodes('//div[contains(@class, "notification")]'.
        '/div/text()[normalize-space(.)!=""]')) {
    print $_->findvalue('normalize-space(.)'), "\n";
}

(normalize-space is used to remove excessive white-space and skip white-space empty HTML nodes. this is an XPath function and similar to the Perl s/^\s*//;s/\s*$//;s/\s+/ /g expression)

now we need to go back to our shell script and modify it to send the SMS. while we’re at it, we decide that at most 1 SMS per hour should be sent to save our money and to avoid SMS bombing when there are frequent changes. Alas we replace the line with send-notification.pl like such:

send_recent=(googl-monitor.sentsms(Nmh-1))
if [[ $#send_recent -eq 0 ]] {
    perl -CA nonoh-sms.pl "$(<googl-result.$$)"
    touch googl-monitor.sentsms
}

here, a -CA switch was added to Perl, which makes it decode the command line arguments as Unicode. Otherwise, the message would be decoded with the wrong encoding, leading to garbled non-ASCII characters in the SMS.

summary

In this part, we have written a shell script to notify us whenever the result output of a specified program changes. To do that, we came up with a Perl script that sends SMS to our mobile phone through a web interface. We have seen how to use glob qualifiers for file cookies, use the cmp tool, and how to process log-ins and dump forms afterwards using WWW::Mechanize. Further, XPath functions were used for our convenience.

Next time we might take a look how to keep the scripts running, how to make calls, or send Jabber messages. Stay tuned.

sample

Thu Jun 14 00:00:10 CEST 2012 Children learn German: songs, games, activities … - Hello-World — http://www.hello-world.com/German/index.php checking with nonoh.net … log in … sending sms … Message has been sent ← this is the output found in the resulting web site log out …