perl.gg / snippets

Starter Template

2024-10-31

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:
#!/usr/bin/perl use strict; use warnings;
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.

Part 2: UTF-8 EVERYWHERE

Modern Perl should handle Unicode properly:
use utf8; use open ':std', ':encoding(UTF-8)'; binmode(STDOUT, ':encoding(UTF-8)'); binmode(STDERR, ':encoding(UTF-8)');
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.

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:
use Term::ANSIColor qw(:constants); print GREEN, "Success!", RESET, "\n"; print RED, "Error!", RESET, "\n"; print BOLD, BLUE, "Important", RESET, "\n";
The :constants import gives you color names you can use directly. Always RESET at the end, or your terminal stays colored. Available colors include:
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:
use FindBin qw($RealBin $RealScript); print "I am: $RealScript\n"; print "I live in: $RealBin\n";
$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.

Part 5: DEBUGGING MADE PRETTY

Data::Dumper is invaluable, but the defaults are ugly:
use Data::Dumper; $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);
With these settings, output is compact and consistent. Sortkeys is especially nice - hashes print the same way every time, making diffs meaningful.

Part 6: THE DISPATCH TABLE

For scripts with multiple commands, use a dispatch table:
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'}->(); }
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.

Part 7: UTILITY FUNCTIONS

Here are three functions I include in almost every project:
sub sleepyTime { my ($max_seconds) = @_; $max_seconds //= 1; select(undef, undef, undef, rand($max_seconds)); }
A random delay up to the given number of seconds. Great for rate limiting, animations, or simulating network latency.
sub typeWriter { my ($text, $delay) = @_; $delay //= 0.05; $|++; $text =~ s`.` select(undef,undef,undef,rand($delay)); print $& `sger; }
The typewriter effect from our earlier tutorial. Pass it text and an optional max delay per character.
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; }, }; }
The string scanner from that tutorial too. Closure-based lexical scanning.

Part 8: THE COMPLETE TEMPLATE

Here it all is together:
#!/usr/bin/perl use strict; use warnings; use utf8; use open ':std', ':encoding(UTF-8)'; use Term::ANSIColor qw(:constants); use FindBin qw($RealBin $RealScript); use Data::Dumper; 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:
- 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
Save your template somewhere accessible and copy it when starting new projects. Future you will thank present you.

Happy coding, and may your scripts always start clean!

Created By: Wildcard Wizard. Copyright 2026