Posts Tagged scripting

To Mail a File Elegantly – Python or Perl?

People fixate on particular languages and stir hot debates over the entrails of code compared in the cold hard light of a walk through but is the debate over which language is better worth even the energy of debate any more? Take for example a trivial task such as regularly sending a file via email, a simple administrative task. There was a day in which Java didn’t have Java mail and Perl didn’t have its elegant MIME::lite so the only way to so things was via bash and some mail client written in C. But today every language has mail client tools built into it, in fact so many of the modern languages like Python and PHP have so many solutions to the common problems worked out in them is there any point over personal preference?

Personally I still like to gut the compared implementations like a fish and have a look. Partly because these questions are fascinating to me, partly because I like to take the opportunity to wind up the religious proponents of each language, and partly because I prefer to gut code rather than fish. Your code might stink but never as much as fish guts.

There is one other reason that I like to perform comparative code autopsies and that is to get into the mind of the people it affects. So lets talk Python, the language of all things Gnome and Perl the language of the data miner. These two languages and their communities are very interesting. Our gnomes have only been, comparatively, around for a short period of time and tend to look at code for code’s sake. Yes python might be the language of GUIs and building blocks are important; but they’re not everything. On the other hand we have the gristled old data miners emerging from their day at the data face deep in their mine chiselling out useful information from the earthy data buried three fathoms deep in some odious data warehouse. They’re all about getting the task done, producing the goods for their team and finding the information that their users want. So there’s the stage a constructive pantheon of building block gnomes and the old data hounds seeking useful information.

Let’s set a comparison task now and see how their favourite tools stake up. But we have to be careful. If we choose a GUI task the miners will go on strike; alternatively if we select a data parsing task the gnomes will sit on their treasures and grump. Hence the task that I’ve chosen favours neither group, it’s stupidly simple and it is something any system administrator can relate to; creating a script to email a file. The rules of the game are:

  • Each language must use its own mail tool set, no invoking mutt or any other such tool,
  • For input the script must take all of the information: SMTP email server address, from address, to address, and fully qualified file name from the command line,
  • A help text and minimal useful input validation along with error reporting must be included, but trivially,
  • Only one file at a time is to be handled,
  • Simple text in the body and subject must be hard coded in the script body with the exception of file name,
  • The file must be in an attachment, not in-line Base64 and should be able to be of any type, not necessarily specified by MIME type,
  • This task is stupidly simple, so so should the script be.

So to the breach!  First up the Perl.

#!/usr/bin/perl

use MIME::Lite;

$address=$ARGV[0];
$from=$ARGV[1];
$to=$ARGV[2];
$path=$ARGV[3];

$usage='filemail.pl smtp from to path';
die "Usage: $usage\n" if $#ARGV != 3;

( $dir, $name ) = ($path=~m/(.*)[\\\/](.+)/) ? ( $1, $2 ) : ( undef, $path );
open FILE, $path or die "Could not find file '$path'";
close FILE;

$msg = MIME::Lite->new(
    From     => $from,
    To       => $to,
    Cc       => '',
    Subject  => "File '$name'.",
    Data     => "Please find file '$name' attached."
  );

$msg->attach(
    Type        =>  'application/octet-stream',
    Path        =>  $path,
    Filename    =>  $name,
    Disposition =>  'attachment'
  ); 

MIME::Lite->send('smtp', $address, Timeout=>60);
$msg->send();

Next the Python.

#!/usr/bin/python

import os
import smtplib
import email
import email.mime.text
import email.mime.multipart
import optparse

# set up the command line parser and grab the args
parser = optparse.OptionParser(usage="""\
Send file attached to a MIME message.

Usage: %prog [options]
""")
parser.add_option('-a', '--address',
                  type='string', action='store', metavar='ADDRESS',
                  help="""Address of the SMTP server (required)""")
parser.add_option('-p', '--path',
                  type='string', action='store', metavar='PATH',
                  help="""Location of the file to send (required)""")
parser.add_option('-s', '--sender',
                  type='string', action='store', metavar='SENDER',
                  help='The value of the From: header (required)')
parser.add_option('-r', '--recipient',
                  type='string', action='append', metavar='RECIPIENT',
                  default=[], dest='recipients',
                  help='A To: header value (at least one required)')
opts, args = parser.parse_args()

# bomb with help if the required arguments are not present
if not opts.path \
        or not opts.sender \
        or not opts.recipients \
        or not opts.address \
        or not opts.path:
    parser.print_help()
    os.sys.exit(1)
if not os.path.isfile(opts.path):
    print 'File \'' + opts.path + '\' not found.'
    os.sys.exit(1)

# create the enclosing (msg) message
filename = opts.path.split('/')[-1]
msg = email.mime.multipart.MIMEMultipart()
msg['Subject'] = 'File \'' + filename + '\'.'
msg['To'] = ', '.join(opts.recipients)
msg['From'] = opts.sender
msg.preamble = 'Please find file \'' + filename + '\' attached.\n'

# message body
body = email.mime.multipart.MIMEMultipart('alternative')
body.attach(email.mime.text.MIMEText('Please find file \'' + filename + '\' attached.\n'))
body.attach(email.mime.text.MIMEText('Please find file \'' + filename + '\' attached.\n', 'html'))

msg.attach(body)

# attach the file
fp = open(opts.path, 'rb')
att = email.mime.base.MIMEBase('application', 'octet-stream')
att.set_payload(fp.read())
fp.close()
email.encoders.encode_base64(att)
att.add_header('Content-Disposition', 'attachment', filename=filename)

msg.attach(att)

# send the message
composed = msg.as_string()
s = smtplib.SMTP(opts.address)
s.sendmail(opts.sender, opts.recipients, composed)
s.quit()

Now for my taste there is no contest. I find myself in the company of wrinkled old miners covered in the dust of ages of data mining experience and dutiful determination. So why did I choose to use the tool of gnomes? Well several reasons really but first lets just make a couple of observations about the code. Neither good or bad, just observations. Then we’ll come back to the decision over choice and the ultimate answer to the ultimate question, is there really any difference of substance between the languages?

  • For shear size Perl has the smaller amount of code.
  • In detail Python is very revealing, it shows some of the nature and structure of the thing that it is building.
  • Considering layout Perl provides us with more opportunities to lay things out for current and future reviewers.
  • In terms of handiness Python provides a wonderful tool for handling standard command line parsing and help generation.
  • Results from either implementation are near enough to identical where it counts.

Now the key here in this list is the last item, where it counts, in other words the results of running both of these scripts, is near enough to identical. The function for which these scripts was written has been achieved within the rules of the game. The only things that we are left debating are the non-functional aspects of the implementation, size, process description, navigation and flexibility. To me the elegance of the Perl solution simply trumps the Python implementation cold. But that’s personal preference based on ascetics. Python proponents will argue that the elegance of Perl is simply the result of a library which could quite easily have been created in Python. Which is true. But you see it isn’t. If it was it would have been. To expand; this is symptomatic of the perspective of the two different communities. Those playing with perls of wisdom know that scripting is all about getting things done, so they ask themselves what is the minimum amount of information that I can get away with providing to get the results that I want. Speed to an end. Focus on results. In the garden of code we find our gnomes building beautiful frameworks which can be used for all sorts of things, generalisation is the key, making it work for the world. Of course you can do mail with Python, but we would rather have a flexible solution for the masses than elegant solutions for the minorities.

To answer the question is there any point to using one language over another other than preference? No. However if you are sitting in a camp of gnomes armed to the teeth with pick axes then all hail the magnificent Python, but if you don’t want to get a shovel through your head after a day in the data mines polish your Perl. At the end of the day there is no real big difference between all of these languages, and if you are like me and sit in the camp with gnomes and miners just pick the one that means the least work for you. In my case it was just more expedient to deploy Python.

, , , ,

6 Comments

Timing Issues with pyinotify


Ok so lets look a that little issue with pyinotify, and it really is only little, but enough to keep you on your toes. So this is the issue in brief.

START LOOP 2009-05-11 21:55:39.733393
Result of notifier.check_events() = TRUE
Calling notifier.process_events()
<Event dir=False mask=0x20 maskname=IN_OPEN name=foo path=/tmp pathname=/tmp/foo wd=1 >
END LOOP 2009-05-11 21:55:40.339782
START LOOP 2009-05-11 21:55:40.339807
Result of notifier.check_events() = TRUE
Calling notifier.process_events()
<Event dir=False mask=0x4 maskname=IN_ATTRIB name=foo path=/tmp pathname=/tmp/foo wd=1 >
END LOOP 2009-05-11 21:55:40.340403
START LOOP 2009-05-11 21:55:40.340426
Result of notifier.check_events() = TRUE
Calling notifier.process_events()
<Event dir=False mask=0x8 maskname=IN_CLOSE_WRITE name=foo path=/tmp pathname=/tmp/foo wd=1 >
END LOOP 2009-05-11 21:55:40.340971


So here is the culprit notifier = pyinotify.Notifier(m, e(), 0, 0, 1000) (I’ve altered the timeout to accentuate the issue). Take a careful look at the timings between the event output lines and you can see that they are uneven. This is because the timeout isn’t a spacer, its just a wait and the events are not linearly spaced.


My error was my own of course. The lesson here is read the code through twice and check it more for design flaws.

,

No Comments

Squid Grep

Just a wee note to remind myself of the best way to view your squid.conf file if you’re in a hurry:

'cat squid.conf | grep -v ^# | grep -v ^$'

Its interesting to note that many people recommend stripping the comments from the active squid.conf with a command similar to this:

'cat /etc/squid/squid.conf | tee /etc/squid/squid.conf.commented \
 | grep -v ^# | grep -v ^$ > /etc/squid/squid.conf.nocomment'

Naturally you would normally not have ‘.nocomment’ on the active file.  You need to be root to do this too.

No Comments

A Few Ajax Links

AJAX is a now a well established technology. It has been around for a long time but only recently (2005) was the term ‘AJAX’ coined to mean Asynchronous JavaScript And XML and unify the previously disjointed technologies it represents. Essentially it is an amalgamation of technologies to provide good user interface tools for Web browser applications.

Now of course there are a myriad of these toolkits, most born soon after the publishing to the AJAX article in 2005, and most not really suitable IMHO for real application. But they are all useful to look at. For me the most interesting ones are the following:

There is pretty good AJAX web site here if you need a place for starting to look for AJAX resources.

,

No Comments

Nautilus Image Scripts

My wife and I do a lot of image processing of our family photos plus the photos of Lisa’s balloons. The most common things that we want to do is to rotate images by 90 degrees to make a portrait photo upright and rename the imagest to be named by the date and name. To do this I simply wrote a few bash scripts and put them in the ‘.gnome2/nautilus-scripts’ directory, then they appeared in the scripts list of the right click context menu in the nautilus file browser windows. This is especially useful as it allows you to select a whole bunch of photos and right click them to get them changed all at once. Note that the scripts do presume a hard wired temporary directory to use for the transforms and also require the exiftool to be accessible on the system.

name_to_date_and_time.sh

#!/bin/bash
TMP_FILE=`tempfile 2> /dev/null` || TMP_FILE="/tmp/nautilus-script.$$"
IFS="
"

trap "rm -f $TMP_FILE" EXIT

for F in $NAUTILUS_SCRIPT_SELECTED_FILE_PATHS; do
  cd `dirname $F`
  mv $F `exiftool -T -d "%Y-%m-%d %H-%M-%S" -createdate $F`
done

rotate_left.sh

#!/bin/bash
TMP_FILE=`tempfile 2> /dev/null` || TMP_FILE="/tmp/nautilus-script.$$"
IFS="
"

trap "rm -f $TMP_FILE" EXIT

for F in $NAUTILUS_SCRIPT_SELECTED_FILE_PATHS; do
  cd `dirname $F`
  mv -f $F $TMP_FILE
  jpegtran -copy all -rotate 270 -outfile "$F" $TMP_FILE
done

rotate_right.sh

#!/bin/bash
TMP_FILE=`tempfile 2> /dev/null` || TMP_FILE="/tmp/nautilus-script.$$"
IFS="
"

trap "rm -f $TMP_FILE" EXIT

for F in $NAUTILUS_SCRIPT_SELECTED_FILE_PATHS; do
  cd `dirname $F`
  mv -f $F $TMP_FILE
  jpegtran -copy all -rotate 90  -outfile "$F" $TMP_FILE
done

, ,

No Comments