Here is my Perl "skeleton" (starter) script (what do you use?)

U

usenet

I use a somewhat elaborate skeleton (starter, template) script to help
me code to "good practices" (I wouldn't presume to say "best
practices"), and to discourge me from thinking bad thoughts like, "I'll
add in good error reporting later." I thought I would post my skeleton
here in hopes someone may find it (or parts of it) useful. Others are
encouraged to post their skeletons here as well.

Most of my scripts run in batch mode (from crons, etc), so my skeleton
is skewed towards that type of environment (no fancy terminal controls,
etc). My "good practices" include:
- Proper POD documentation (including versioning and revision)
- Command-line parameter handling (via GetOpt::Long), inc'l --help
- External configuration file handling (via Config::IniFiles)
- Logging to screen, file, and e-mail (via Log::Dispatch)
- Bail out if the script is already running.
- Pretty formatting of setup blocks and subroutines.
- Custom handlers for warn() and die().
- Define and detect killfiles.
- Get handles to a database and sftp ('cause I do that a lot).

Some of this gets a bit sophisticated. For example, the script uses
Config::IniFiles to parse a configuration file which is stipulated on
the commandline (via "--config=/path/to/my.ini"), OR it will use a
default filename of "myscript.ini" (assuming the script is named
"myscript.pl"), OR it will process configuration information embedded
within the script itself (under a __DATA__ token).

Generally, any of these sections can be safely deleted without
impacting other sections (though the little bit of skeleton code in the
main driver may throw 'strict' errors because some variable declaration
got deleted).

Anyway, here it is:


#!/usr/bin/perl
# Skeleton program. See end for POD

$main::VERSION = '0.01'; #see POD for history

###################################################################
####### ENVIRONMENT ----- ENVIRONMENT ----- ENVIRONMENT ########
###################################################################
use strict; use warnings; #always, always ##
#use diagnostics; ##
##
### Other skeleton stuff needs these... check before you delete ##
use English; #for (English) readability ##
use FindBin; ##
use Sys::Hostname; ##
##
### Optional stuff that I use often ##
use lib $FindBin::Bin; #also find modules in the program dir ##
use Fatal qw:)void open opendir chdir rename); ##
use IO::All; ##
use Data::Dumper; ##
use XML::Simple; ##
use Date::Manip; ##
##
select(STDERR); $| = 1; select(STDOUT); $| = 1; #unbuffered ##
##
###################################################################


####################################################################
### OPTIONS #######################################################
####################################################################
##
use Pod::Usage; ##
use Getopt::Long qw:)config auto_version); ##
my %opt; ##
GetOptions ( \%opt, # always include the first two... ##
'help|h|?', # Show help (POD) --help or --? ##
'man', # Show manpage (POD) --man ##
'test', # Run program in test mode ##
#here are some usage examples of of other parameter types ##
'config|ini=s', # Alternate configuration (.ini) file ##
'string_opt=s', # Required String --config=/tmp/x.ini ##
'integer_opt:i', # Optional Integer --border[=2] ##
'def_int_opt:5', # Optional integer but with default value ##
'option', # Boolean (true) --option ##
'opt2!', # Boolean (negatable) --opt2 or --no-opt2 ##
'more+' # Incrementable --more --more --more ##
) || pod2usage( {-verbose => 1} ); ##
#print map {"\t$_\t$opt{$_}\n"} sort keys %opt; #die "\n"; ##
##
pod2usage( {-verbose => 1} ) if $opt{'help'}; #|| ! @ARGV; ##
pod2usage( {-verbose => 2} ) if $opt{'man'}; ##
##
#Set default values if you want to ##
$opt{'string_opt'} ||= 'foo'; ##
##
####################################################################


####################################################################
### CONFIGURATION FILE #############################################
####################################################################
# Assumes script is named like 'myscript.pl' and config file ##
# is named like 'myscript.ini' (in the same directory) unless ##
# specified with a command-line config=/whatever/is.ini option ##
# OR - you can put the config info after an __END__ token ##
##
use Config::IniFiles; ##
##
my %cfg; ##
##
eval {no strict 'vars'; #in case the GetOpts block was deleted ##
$cfg{'inifile'} = $opt{'config'} # if defined $opt{'config'} ##
|| ("$FindBin::Bin/$FindBin::Script" =~ /(.*)\.pl/)[0].'.ini'; ##
}; ##
if (-e $cfg{'inifile'}) { ##
tie %cfg, 'Config::IniFiles', ( -file => $cfg{'inifile'} ) ##
or die @Config::IniFiles::errors; ##
}elsif (<DATA>) { ##
tie %cfg, 'Config::IniFiles', ( -file => *main::DATA ) ##
or die @Config::IniFiles::errors; ##
}else{ ##
warn "No Configuration info found: $cfg{'inifile'}\n"; ##
} ##
##
#Define default killfile (in addition to any that may be defined) ##
$cfg{'killfile'}{"/tmp/$FindBin::Script.kill"}++; ##
##
####################################################################


####################################################################
######### VALIDATE ###### VALIDATE ###### VALIDATE ##########
####################################################################
do{ #use 'do' to lexically isolate variables ##
my $ps = "/usr/bin/ps"; #crude, but it works for my purposes ##
die "Already Running\n" if scalar grep(/perl.*$0/, `$ps -ef`) >1; ##
##
-e && unlink for keys %{$cfg{'killfile'}}; #remove old killfiles ##
}; #do ##
####################################################################


####################################################################
### LOGGING. Levels are: ###########################################
### debug info notice warning error critical alert emergency ##
####################################################################
##
use Mail::Sendmail; ##
use Log::Dispatch; ##
use Log::Dispatch::Screen; ##
use Log::Dispatch::File; ##
use Log::Dispatch::Email::MailSendmail; ##
##
my $log; #or maybe 'our log' for some conveniences ##
##
do{ #use 'do' to lexically isolate variables ##
my $logdir = $cfg{'dir'}{'log'} || "/var/tmp"; ##
my @admin_email = @{$cfg{'email'}{'admin'}} || ##
sprintf('%s@%s', scalar getpwuid($<), hostname);##
##
unshift @{$Mail::Sendmail::mailcfg{'smtp'}}, ##
$cfg{'email'}{'smtp'} || 'localhost'; ##
##
my $add_lf = sub { my %p = @_; "$p{'message'}\n"}; ##
my $add_timestamp = sub { my %p = @_; ##
sprintf "%s - %s", scalar(localtime), ##
$p{'message'}; }; ##
##
$log = Log::Dispatch->new ( callbacks => $add_lf ); ##
##
$log ->add( Log::Dispatch::Screen ->new( ##
name => 'screen', ##
min_level => 'debug', ##
stderr => 0, ) ##
); ##
$log ->add( Log::Dispatch::File ->new( ##
name => 'file', ##
min_level => 'debug', ##
filename => sprintf ( "%s/%s.log", ##
$logdir, ##
$FindBin::Script ), ##
mode => 'append', ##
callbacks => $add_timestamp ), ##
); ##
$log ->add( Log::Dispatch::Email::MailSendmail ->new( ##
name => 'email', ##
min_level => 'error', ##
to => \@admin_email, ##
subject => "ERROR in $PROGRAM_NAME", ##
from => sprintf ("SERVER<%s\@%s>", ##
(hostname =~ /^([^\.]*)/)[0], ##
'do_not_reply.com' ) ), ##
); ##
}; #do ##
#dispatch our very first message - print all the runtime info ##
$log -> debug(sprintf ##
"[%s]\tBegin %s\n\tVersion %s on %s as %s\n" ##
."\tConfigFile: %s\n\tKillfile(s):\n%s", ##
__LINE__, __FILE__, ##
$main::VERSION, ##
hostname(), ##
"$REAL_USER_ID ($ENV{'USER'})", ##
$cfg{'inifile'}, ##
map {"\t\t$_\n"} keys %{$cfg{'killfile'}}, ##
); ##
####################################################################


####################################################################
### DATABASE #######################################################
####################################################################
##
use DBI; use DBIx::Simple; ##
##
my $dbh = DBIx::Simple -> connect( ##
sprintf ("dbi:%s:host=%s;database=%s", ##
$cfg{'dbi'}{'type'} || 'mysql', ##
$cfg{'dbi'}{'host'} || 'localhost', ##
$cfg{'dbi'}{'database'} || $FindBin::Script ), ##
$cfg{'dbi'}{'user'} || $ENV{'USER'}, ##
$cfg{'dbi'}{'password'} || '', ##
{ PrintError => $cfg{'dbi'}{'PrintError'} || 0, ##
RaiseError => $cfg{'dbi'}{'RaiseError'} || 0, ##
AutoCommit => $cfg{'dbi'}{'AutoCommit'} || 0, } ##
) or $log -> critical ( DBIx::Simple->error() ); ##
##
####################################################################


####################################################################
### SFTP ###########################################################
####################################################################
##
use Net::SFTP; ##
my $sftp = Net::SFTP->new($cfg{'sftp'}{'host'}, %{$cfg{'sftp'}}) ##
|| $log -> critical("SFTP ERROR: $!"); ##
##
####################################################################




#### # # ##### ####
# # # # # #
#### # # ##### ####
# # # # # #
# # # # # # # #
#### #### ##### ####

####################################################################
sub Dummy($) { ########################################
####################################################################
#Prints passed hash via Dummy({'foo'=>'bar'}) or Dummy(\%baz) ##
my %param = %{$_[0]}; ##
$param{'log'} -> debug( ##
map { "'$_'\t=>\t'$param{$_}'\n"} sort keys %param ); ##
} ##################################################################




##### ##### # # # ###### #####
# # # # # # # # # #
# # # # # # # ##### # #
# # ##### # # # # #####
# # # # # # # # # #
##### # # # ## ###### # #

#Define custom 'warn' and 'die' handlers
local $SIG{__WARN__} = sub {
$log->warning("OH NO! Look what happened:\n@_");
};
local $SIG{__DIE__} = sub {
$dbh->disconnect if $dbh;
$log->critical("I'm Dead, Jim!\n@_");
};


################################################
###-------- Main Program Goes Here! ---------###
################################################

#Withing the program, check and die if a killfile is detected:
-e && die "Killfile $_ Detected.\n" for keys %{$cfg{'killfile'}};

Dummy({'foo' => 'bar', 'log' => $log}); #call a sub


#### Ready for the script to end normally ####

$dbh->disconnect if $dbh; #don't forget!
$log->debug(sprintf "\n\nFinished %s - %s Seconds Elapsed",
scalar localtime,
time - $BASETIME,
);
exit(0);

__DATA__

#sample Config::IniFiles configuration file. If the external file
#doesn't exist, the information will be read directly from here.

[example]
foo = bar
#When read, $cfg{'example'}{'foo'} eq 'bar'

[sftp]
host = localhost
user = my_userid
password = my_password

[dbi]
database = test

(e-mail address removed)
 
M

Matija Papec

I use a somewhat elaborate skeleton (starter, template) script to help
me code to "good practices" (I wouldn't presume to say "best
practices"), and to discourge me from thinking bad thoughts like, "I'll
add in good error reporting later." I thought I would post my skeleton
here in hopes someone may find it (or parts of it) useful. Others are

You have much room for improvements on your "skeleton"; that is, if
you're into finding a better ways/practices.
 
U

usenet

Matija said:
You have much room for improvements on your "skeleton"; that is, if
you're into finding a better ways/practices.

I'm always looking for improvements. But I thought my script was
pretty good (most of it was taken straight from the perldocs of the
various modules I'm using). However, I would be very interested to
know of any specific suggestions you have to improve it.
 
J

John Bokma

I'm always looking for improvements. But I thought my script was
pretty good (most of it was taken straight from the perldocs of the
various modules I'm using). However, I would be very interested to
know of any specific suggestions you have to improve it.

A skeleton I used was:

#!/usr/bin/perl

use strict;
use warnings;

but I put it in the closet :)
 
U

usenet

John said:
A skeleton I used was:

#!/usr/bin/perl

use strict;
use warnings;
Yeah, that's what I used for years. It's the only startup that I would
presume to call "best" practices. But I found myself doing (or
intending to do) the same sorts of other things (error handling, etc)
and I was always doing cut-and-paste from other scripts, so I finally
decided to group all my "other" stuff together.
but I put it in the closet :)

<g> so do you need a skeleton key to open it? (sorry, sorry, I'll keep
my day job)
 
A

Ala Qumsieh

Yeah, that's what I used for years. It's the only startup that I would
presume to call "best" practices. But I found myself doing (or
intending to do) the same sorts of other things (error handling, etc)
and I was always doing cut-and-paste from other scripts, so I finally
decided to group all my "other" stuff together.

Instead of a skeleton, you should be creating your own library (or
libraries) which you can 'use' in your scripts as needed. Unless you're
constantly writing very similar scripts, I can not really see how one
skeleton would fit all requirements of every script.

--Ala
 
M

Matija Papec

X-Ftn-To: (e-mail address removed)
I'm always looking for improvements. But I thought my script was
pretty good (most of it was taken straight from the perldocs of the

You can separate some of your init routines into module, and call them only
when you actually need them, eg.

my $dbh = $cfg{dbi} && YourModule::DBIconnect($cfg{dbi});
It also saves you time when in need for multiple db connections.

The same goes for sftp; you could be connecting to something even when you
don't have parameters in $cfg{'sftp'}.

#use diagnostics; ##
##
### Other skeleton stuff needs these... check before you delete ##
use English; #for (English) readability ##

English module has unfortunate defaults so it adds some overhead, but this
is easily "fixed" if you don't need some of costly regex features.
==========
SYNOPSIS
use English qw( -no_match_vars ) ; # Avoids regex performance penalty

my %cfg; ##
##
eval {no strict 'vars'; #in case the GetOpts block was deleted ##
$cfg{'inifile'} = $opt{'config'} # if defined $opt{'config'} ##
|| ("$FindBin::Bin/$FindBin::Script" =~ /(.*)\.pl/)[0].'.ini'; ##
}; ##

I guess you don't actually need eval as you're not inspecting $@ after it,
so pure block with "no strict 'vars'" better reflects real intention.

####################################################################
######### VALIDATE ###### VALIDATE ###### VALIDATE ##########
####################################################################
do{ #use 'do' to lexically isolate variables ##

nothing wrong here, you can also drop "do" and use pure block (btw, from
which you can exit at any point).

MY_BLOCK: {
...
last MY_BLOCK if ...;
...
}
my $ps = "/usr/bin/ps"; #crude, but it works for my purposes ##
die "Already Running\n" if scalar grep(/perl.*$0/, `$ps -ef`) >1; ##
##
-e && unlink for keys %{$cfg{'killfile'}}; #remove old killfiles ##

unlink grep -e, keys %{$cfg{killfile}};

more style related; "for" modifier is nice but it can also grow too much and
kill initial simplicity.
 
A

axel

John Bokma said:
A skeleton I used was:

#!/usr/bin/perl

use strict;
use warnings;

but I put it in the closet :)

abbr #p #!/usr/bin/perluse strict;use warnings;

in my .exrc file.

Axel
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,906
Latest member
SkinfixSkintag

Latest Threads

Top