Net::SSH::Perl, Perlbrew and You

For all of the admins who threw their computer out the window and swore they would never use CPAN again when they tried to install Net::SSH::Perl, try the following two things:
1. Install the GMP library(not the perl one) apt-get install libgmp-dev
2. Use cpanm instead of cpan. i.e. cpanm Net::SSH::Perl

This should solve the following error:
GMP.xs:4:17: error: gmp.h: No such file or directory

Also, while I am talking about it, check out perlbrew. It allows you to easily set up self contained instances of perl for multiple versions and switch between them. The possible uses for this are nearly endless, but I find that it increases my “sense of adventure” when messing with perl modules and my environment since I am not affecting the system perl.

Perlbrew

Perl invocation options

I have known about perl invocation options for a while, and I have used them on several occasions. However, I’m not sure I understood just how far the rabbit hole goes.

Try the following:

perl -wnle 'print "Each line of the file is printed here $_";' foo #Process through each line.
perl -wpie 's/replace/with/g;' foo # sed -i replacement
perl -wnlae 'print $F[0]' foo # awk '{print $1}' replacement

The best part is that these go beyond just replacing existing shell commands because of all the powerful constructs that exist in perl. If you are interested in more of the same, check out the linked book. Now if you will excuse me I am going to go follow the white rabbit…

Parsing linux fortune files into mysql

As a part of my recent project to build a web based fortune cookie, I wrote the following script to parse fortune files and insert them into a database table. Most of this code is relevant regardless of what you want to do with the data.

It makes use of database handles which I described here.

The only really new thing in this script is a state machine to handle multiple lines of quote text. I have removed the non essential elements here.

# read each line from the file
while(<FH>){
        # set the state to "in quote"
        $in_quote=1;
        # % indicates the end of a fortune
        if ($_ eq '%'){
                # run query

                # reset our state machine
                $in_quote=0;
                $author='';
                $text='';
        }
        elsif ($in_quote == 1 && $_ =~ m/^[\s|\t]+-- (.*)$/){
                $author="$1";
        }
        elsif ($in_quote == 1 && !($_ =~ m/^$/)){
                $text.="$_<BR />";
        }
}

As you may be able to see, this type of behavior can be used to parse a great many things that have a beginning and ending within a file. A more sophisticated version could be used to parse an apache config file, an xml file or something similar.

#!/usr/bin/perl

use warnings;
use strict;
use DBI;

# variable definition
my $dbuser = "redacted";
my $dbpass = "redacted";
my $db     = "redacted";
my $dbhost = 'localhost';
my $in_quote;
my $text;
my $author;
my $query;
# open the file

open(FH,"$ARGV[0]") || die "cannot open file $ARGV[0]";

# connect to the database

my $dbh = DBI->connect("DBI:mysql:$db:$dbhost", $dbuser, $dbpass) || die "Could not connect to database: $DBI::errstr";
$dbh->{PrintError} = 1; # do this, or check every call for errors

# prepare some queries we are likely to use
# note the use of q{} to protect the quotes in the statement
my $sthInsertRecord = $dbh->prepare(q{
    INSERT INTO fortunes (`text`, `author`)
    VALUE (?,?);
});

# read each line from the file
while(<FH>){
        # handle new lines
        chomp;
        # set the state to "in quote"
        $in_quote=1;
        if ($_ eq '%'){
                # i limited my table column to 255 characters to keep it fast
                if(length($text) <= 255){
                        # execute the query
                        $sthInsertRecord->execute($text, $author);
                }
                # reset our state machine
                $in_quote=0;
                $author='';
                $text='';
        }
        # if there is an author, it is indicated by a leading --
        elsif ($in_quote == 1 && $_ =~ m/^[\s|\t]+-- (.*)$/){
                $author="$1";
        }
        # if it isn't the terminator and it isn't an author then it is text
        elsif ($in_quote == 1 && !($_ =~ m/^$/)){
                $text.="$_<BR />";
        }

}
#clean up
$sthInsertRecord->finish;
$dbh->disconnect();
close(FH);

Deploy your SSH key with Perl

We’ve all been there, we have a bunch of linux boxes with user accounts set up and no SSH keys. If you are like most linux users out there you have probably read more than a few guides on setting up simplified access using SSH keys, but one thing that seems to be missing is a guide on how to efficiently deploy your SSH keys so that you don’t have to login to dozens or hundreds of machines. Enter the script below.

Continue reading

Simple Perl Logging Subroutine

In the course of programming perl I have often found it useful to have the ability to quickly write to a logfile. I wrote the subroutine below to assist in debugging my scripts.

my $logdir="/var/log/";
sub logit
{
    my $s = shift;
    my ($logsec,$logmin,$loghour,$logmday,$logmon,$logyear,$logwday,$logyday,$logisdst)=localtime(time);
    my $logtimestamp = sprintf("%4d-%02d-%02d %02d:%02d:%02d",$logyear+1900,$logmon+1,$logmday,$loghour,$logmin,$logsec);
    $logmon++;
    my $logfile="$logdir$logmon-$logmday-logfile.log";
    my $fh;
    open($fh, '>>', "$logfile") or die "$logfile: $!";
    print $fh "$logtimestamp $s\n";
    close($fh);
}

This subroutine makes writing to a logfile as simple as the following.

logit("Something somthing $variable");

Not the most complicated subroutine, but it certainly gets the job done. I think the coolest part of the whole script is probably splitting localtime into multiple variables and then using sprintf() to reassemble them into an appropriate timestamp.