Starter Template
Every Perl project starts somewhere. This is my battle-tested starter template - the boilerplate I copy into every new script. It handles the annoying stuff so you can focus on the interesting problems.Part 1: THE ESSENTIALS
Every script starts with this:You know these. strict catches variable typos and requires declarations. warnings tells you when you're doing something questionable. Never skip them, even for "quick scripts." Quick scripts have a way of becoming permanent.#!/usr/bin/perl use ; use ;
Part 2: UTF-8 EVERYWHERE
Modern Perl should handle Unicode properly:The first line says "this source file contains UTF-8." The second makes file handles default to UTF-8. The binmode calls ensure STDOUT and STDERR handle Unicode correctly.use ; use open ':std', ':encoding(UTF-8)'; binmode(STDOUT, ':encoding(UTF-8)'); binmode(STDERR, ':encoding(UTF-8)');
Without these, you'll get garbage when printing characters outside ASCII. With them, print "Hello, δΈη!\n" just works.
Part 3: COLORFUL OUTPUT
Life's too short for monochrome terminals:The :constants import gives you color names you can use directly. Always RESET at the end, or your terminal stays colored. Available colors include:use Term::ANSIColor qw(:constants); print GREEN, "Success!", RESET, "\n"; print RED, "Error!", RESET, "\n"; print BOLD, BLUE, "Important", RESET, "\n";
BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE BRIGHT_* versions of all the above BOLD ITALIC UNDERLINE (and more)
Part 4: FINDING YOUR WAY
Scripts need to know where they live:$RealBin is the directory containing your script (following symlinks). $RealScript is the script's name. These are essential when your script needs to find config files or other resources relative to itself.use FindBin qw($RealBin $RealScript); print "I am: $RealScript\n"; print "I live in: $RealBin\n";
Part 5: DEBUGGING MADE PRETTY
Data::Dumper is invaluable, but the defaults are ugly:With these settings, output is compact and consistent. Sortkeys is especially nice - hashes print the same way every time, making diffs meaningful.use ; $Data::Dumper::Indent = 1; # Mild indentation $Data::Dumper::Sortkeys = 1; # Alphabetize hash keys $Data::Dumper::Terse = 1; # No $VAR1 = prefix my %data = (foo => 1, bar => [2, 3, 4]); print Dumper(\%data);
Part 6: THE DISPATCH TABLE
For scripts with multiple commands, use a dispatch table:This is cleaner than a chain of if/elsif statements and easier to extend. Each command is a code reference - either a named sub or an anonymous one.my $dt = { 'help' => \&show_help, 'version' => \&show_version, 'run' => \&do_the_thing, 'test' => sub { print "Testing...\n"; }, }; my $command = shift @ARGV || 'help'; if (exists $dt->{$command}) { $dt->{$command}->(); } else { print "Unknown command: $command\n"; $dt->{'help'}->(); }
Part 7: UTILITY FUNCTIONS
Here are three functions I include in almost every project:A random delay up to the given number of seconds. Great for rate limiting, animations, or simulating network latency.sub sleepyTime { my ($max_seconds) = @_; $max_seconds //= 1; select(undef, undef, undef, rand($max_seconds)); }
The typewriter effect from our earlier tutorial. Pass it text and an optional max delay per character.sub typeWriter { my ($text, $delay) = @_; $delay //= 0.05; $|++; $text =~ s`.` select(undef,undef,undef,rand($delay)); print $& `sger; }
The string scanner from that tutorial too. Closure-based lexical scanning.sub strScan { my ($string) = @_; my $pos = 0; return { pos => sub { return $pos; }, mod_pos => sub { $pos += shift; return $pos; }, eos_check => sub { return $pos >= length($string); }, find => sub { my ($pattern) = @_; my $remainder = substr($string, $pos); if ($remainder =~ /$pattern/) { $pos += $-[0] + length($&); return { match => $&, start => $-[0] }; } return undef; }, }; }
Part 8: THE COMPLETE TEMPLATE
Here it all is together:#!/usr/bin/perl use ; use ; use ; use open ':std', ':encoding(UTF-8)'; use Term::ANSIColor qw(:constants); use FindBin qw($RealBin $RealScript); use ; binmode(STDOUT, ':encoding(UTF-8)'); binmode(STDERR, ':encoding(UTF-8)'); $Data::Dumper::Indent = 1; $Data::Dumper::Sortkeys = 1; $Data::Dumper::Terse = 1; # Dispatch table my $dt = { 'help' => \&show_help, 'run' => \&main, }; # Utility functions sub sleepyTime { my ($max) = @_; $max //= 1; select(undef, undef, undef, rand($max)); } sub typeWriter { my ($text, $delay) = @_; $delay //= 0.05; $|++; $text =~ s`.` select(undef,undef,undef,rand($delay)); print $& `sger; } sub strScan { my ($string) = @_; my $pos = 0; return { pos => sub { return $pos; }, mod_pos => sub { $pos += shift; return $pos; }, eos_check => sub { return $pos >= length($string); }, find => sub { my ($pattern) = @_; my $rem = substr($string, $pos); if ($rem =~ /$pattern/) { $pos += $-[0] + length($&); return { match => $&, start => $-[0] }; } return undef; }, }; } # Your code here sub show_help { print "Usage: $RealScript [command]\n"; print "Commands: ", join(", ", sort keys %$dt), "\n"; } sub main { print GREEN, "Ready to go!", RESET, "\n"; } # Entry point my $cmd = shift @ARGV || 'help'; if (exists $dt->{$cmd}) { $dt->{$cmd}->(); } else { print RED, "Unknown: $cmd", RESET, "\n"; show_help(); }
Part 9: MAKING IT YOUR OWN
This template is a starting point. Over time, you'll develop your own preferences:Save your template somewhere accessible and copy it when starting new projects. Future you will thank present you.- Add your favorite modules (IO::All, Path::Tiny, JSON, etc.) - Include project-specific utilities - Adjust the Dumper settings - Add logging configuration - Include database connection boilerplate
Happy coding, and may your scripts always start clean!
Created By: Wildcard Wizard. Copyright 2026