Monday, October 26, 2009

Small and cute shell and Perl scriptlets

Once upon a time, the world was full of small and cute shell and Perl scripts that served a small but useful purpose, and which were shared freely - just because hackers were nice guys. So we bring some of these around with us, in slightly modified or improved versions.

I'd like to carry on the tradition of sharing some of these before they are lost, even though they are about as trivial as you can get.

Eval

Here's one that I picked up at the University of Oslo 10-15 years ago, which I still use, just out of habit. The file is traditionally named Eval, and I believe this particular version was concocted by Kjetil T. Homme (who has some other nice hacks, BTW). The idea is to have an easy-to-use command line calculator:
#! /bin/sh -

exec perl -e '$a = ('"$*"');
if ($a == int($a)) {
print $a, "\n";
} else {
printf ("%.3f\n", $a);
}'
So what's the point?

For me, it's the ease of use, and not least the ease of installability. All I need is a Perl version that understands a very simple set of commands. I don't have to worry whether bc or dc is installed, I don't have to open a GUI, and I can work with fairly advanced expressions. They just have end up as valid Perl.

7

Did you ever forget exactly which octal code to use for the ASCII letter 'h', or the hex number for the backtick (`) character? 7 to the rescue!
#! /usr/bin/perl -C

for (32..126) {
printf "%3d 0x%02X 0%03o 0b%08b %c\n", ($_)x5;
}
And if you still haven't made the transition to Unicode/UTF-8, printing the printables in e.g. a Latin character set may still be interesting; create a copy of the file called 8, which works on the range 160..255 instead. Or be fancy with basename checking and all that. :)

Answer

When I write scripts that do potentially Dangerous Stuff on production servers (that happens way too often), I usually like to include some code that tells the user to think a bit before continuing. That is, I don't want all scripts to be fire-and-forget. This is another classical piece of code, which I've massaged for my own needs (and perhaps your needs as well?):
print "About to run $code.\n\n";
print "Are you sure? (y/N): ";
my $answer = <STDIN>;
chop $answer;
if ($answer !~ /^y(es)?$/i) {
die "Okay, aborting.\n";
} else {
print "Okay, continuing!\n";
}

Monday, October 19, 2009

183 days of Iron Man blogging

Today it's 183 days - more than half a year - since I started blogging, spurred on by mst's lightning talk at NPW 2009.

That ain't half bad.

But he's still not losing his bet yet, argh! :D

Coding styles that make me twitch, part 5

Let us say that we have some code using DBI - old-fashioned, but it still works, kindof.

How would you like to see the following in a 6000 line CGI script you're supposed to debug?

my $sth=&query("SELECT id FROM invoices WHERE invoiced='N'");
my $invoiced=$sth->rows;

my $sth2=&query("SELECT count(*) nusers FROM users \
LEFT JOIN people ... # long SQL statement
my $row2 = $sth2->fetchrow_hashref;
my $nusers = $row2->{nusers};

my $sth3=&query("SELECT count(*) npeople FROM users \
LEFT JOIN people ... # long SQL statement
my $row3 = $sth3->fetchrow_hashref;
my $npeople = $row3->{npeople};

my $sth4=...
And then, after carefully checking scope, you discover that the variables $sth2, $row2, $sth3, $row3, $sth4, $row4 etc. are not used anywhere else within the same scope.

Would you develop a tick?

I did, and the tick didn't lessen in strength when I discovered unsafe variable interpolation as well as a sub query that used $dbh->prepare($qtext) but didn't allow passing of usable parameters, such as, you know, bind variables.

I've started on "refactoring" the real life code this example is based on, but I get depressed from the sheer amount of work in fixing many problems at once. Maybe I should pick up drunken coding to become less perfectionist.

Monday, October 12, 2009

Use Digest::MD5 - it's easy

In my previous entry, I presented and purposefully ignored a rather non-portable way of getting an MD5 sum from a file:
$md5 = `md5sum $filename.new | awk '{ print $1 }'`;
$md5 =~ s/\n$//;
There are stupider and better ways of doing this in the system call, but it completely ignores the problem that the command is not always called md5sum.

Digest::MD5 comes to the rescue!

use autodie; # Hee-hee
use Digest::MD5;
my $digester = Digest::MD5->new;
open(FH,"<$filename.new");
$digester->addfile(*FH);
my $md5 = $digester->hexdigest;
Okay, that looks slightly over-complicated, one might argue that Digest::MD5 should hide the file handle fiddling from the user. The value added comes when you have a chunk of data already in a variable, then you just do use Digest::MD5 qw(md5_hex); and call md5_hex($data).

Oh, and I snuck in something else again, didn't I.

Tuesday, October 6, 2009

Coding styles that make me twitch, part 4

All too often, I see code like this:

$md5 = `md5sum $filename.new | awk '{ print $1 }'`;
$md5 =~ s/\n$//;
if ($md5 ne $origmd5) {
system("mv $filename $filename.old");
system("mv $filename.new $filename");

}
Well, you get the idea.

Sure, Perl can pretend to be a glorified shell script, but there are perfectly well-functioning internal functions for these things.

Let's ignore the non-portability of the md5sum command for the time being, and also avoid shaving that awk call down with cut and an enclosing echo -n $(…) - we're here for the Perl, not the shell, right?

The above system() calls can easily be replaced with the following to save yourself a few unnecessary forks:

rename($filename,"$filename.old") and
rename("$filename.new",$filename);
Note the slight refinement of using and to avoid clobbering the file. But the documentation for rename() suggests that we use move() from File::Copy instead:

use File::Copy qw(mv);
mv($filename,"$filename.old") and
mv("$filename.new",$filename);
Similarly, there are nice built-in functions for many other frequent victims of system(), e.g.:

chmod
chgrp + chown
link
mkdir
rmdir
symlink
unlink
And of course there is a bunch of more or less helpful modules on CPAN (in addition to the already mentioned File::Copy), e.g. File::Path.