Learning Perl
A comprehensive, structured outline of the Perl programming language — from first principles through advanced mastery. Every major feature, idiom, and best practice in one document.
What is Perl?
Perl (Practical Extraction and Report Language) is a high-level, general-purpose, interpreted, dynamic programming language created by Larry Wall in 1987. It draws from C, shell scripting, awk, sed, Lisp, and natural language, combining them into a flexible tool especially suited for text manipulation, system administration, and rapid development.
Perl's Strengths
Installing & Running Perl
perl --version # check your version perl script.pl # run a script perl -e 'say "Hello"' # inline one-liner perl -c script.pl # syntax check only perl -w script.pl # enable warnings (old way) perl -d script.pl # interactive debugger perl -de 42 # REPL-like debugger session
perlbrew or plenv to manage multiple Perl versions on one machine without touching the system Perl.The Three Virtues of a Programmer (Larry Wall)
| Virtue | Meaning |
|---|---|
| Laziness | Write less total work by automating; write code others can reuse |
| Impatience | Hate waiting; write programs that anticipate your needs |
| Hubris | Write code you won't be ashamed of; take ownership of quality |
Anatomy of a Perl Script
#!/usr/bin/env perl # shebang — tells OS to run with Perl use strict; # require all variables to be declared use warnings; # warn about suspicious constructs use feature 'say'; # enable say(), state, etc. use utf8; # source is UTF-8 encoded use open ':std', ':utf8'; # STDIN/OUT/ERR in UTF-8 my $greeting = "Hello, world!"; say $greeting; exit 0; # explicit exit code (optional)
Key Pragmas
| Pragma | Purpose | Essential? |
|---|---|---|
| use strict | Enforce variable declarations; ban barewords and symbolic refs | Yes — always |
| use warnings | Warn about uninitialized vars, wrong types, deprecated usage | Yes — always |
| use feature 'say' | Enable say (print + newline), state, switch | Recommended |
| use feature ':5.36' | Enable all features introduced by a specific Perl version | Modern style |
| use v5.36 | Shorthand: declares minimum version + enables all its features | Modern style |
| use utf8 | Source file uses UTF-8; allows Unicode identifiers | If using Unicode |
| use constant | Define compile-time constants (inlineable) | When needed |
| use English | Human-readable aliases for punctuation variables ($ARG, $OS_ERROR…) | Optional |
| use Carp | Better error messages that point to the caller, not the callee | In modules |
| use Data::Dumper | Pretty-print data structures for debugging | Debug only |
Statements, Blocks & Comments
# Single-line comment — everything after # is ignored # (except inside strings) my $x = 42; # every statement ends with ; my $y = 10; { # braces create a new lexical scope my $local = $x + $y; # $local is only visible inside {} say $local; } # $local is gone here # Multi-line "comment" using a heredoc trick: =pod This block is POD documentation — ignored by the compiler. It ends with =cut. =cut
if / while blocks. There is no braceless one-liner form.Scalar Variables
A scalar holds exactly one value: a string, number, reference, or undef. The $ sigil always means "give me one thing." Perl converts between strings and numbers automatically based on context.
my $name = "Alice"; # string my $age = 30; # number my $pi = 3.14159; # float my $empty = undef; # no value yet my $flag = 1; # true (no boolean type) # Perl auto-converts based on context: my $s = "42 items"; my $n = $s + 1; # numeric context → 43 (uses "42" part) my $s2 = $n . "!"; # string context → "43!"
String Quoting
| Syntax | Interpolates? | Description |
|---|---|---|
| 'single' | No | Completely literal — only \' and \\ are special |
| "double" | Yes | Variables and \n \t \x{} \N{} escape sequences expanded |
| q(text) | No | Same as single quotes; any delimiter: q|..| q{..} |
| qq(text) | Yes | Same as double quotes; any delimiter: qq|..| qq{..} |
| qw(a b c) | No | List of whitespace-separated words → ('a','b','c') |
| `command` | Yes | Backtick: runs shell command and returns its output |
| qx(command) | Yes | Same as backticks; safer with unusual chars in command |
String Operators
| Operator | Meaning | Example |
|---|---|---|
| . | Concatenate | "foo" . "bar" → "foobar" |
| x | Repeat | "ab" x 3 → "ababab" |
| .= | Concatenate and assign | $s .= " more" |
| eq ne lt gt le ge cmp | String comparison | "foo" eq "foo" → true |
String Functions
| Function | Description |
|---|---|
| length($str) | Number of characters |
| substr($str, $off, $len) | Extract substring; with 4th arg: replace in-place |
| index($str, $sub) | Position of first occurrence (-1 if not found) |
| rindex($str, $sub) | Position of last occurrence |
| uc($str) / lc($str) | Upper / lower case entire string |
| ucfirst / lcfirst | Change case of first character only |
| chomp($str) | Remove trailing \n (or $/); modifies in-place; returns count |
| chop($str) | Remove and return the last character |
| reverse($str) | In scalar context: reverses the string |
| sprintf($fmt, @args) | Format string without printing; same directives as C printf |
| split(/pat/, $str, $lim) | Split string on pattern → array |
| join($sep, @list) | Join array elements into a string with separator |
| pos($str) | Current match position after /g match |
Heredocs
# Interpolating heredoc (like double quotes) my $name = "Alice"; my $text = <<END; Hello, $name. Welcome to Perl. END # Non-interpolating (like single quotes) — quote the label my $raw = <<'END'; Literal: $name is not expanded here. END # Indented heredoc (Perl 5.26+) — ~ strips leading whitespace my $indented = <<~END; This content can be indented to match surrounding code. END
Truthiness in Perl
undef, 0, "" (empty string), "0". Everything else is true — including "00", "0.0", and the string "false".Number Literals
42 # integer 3.14 # float 6.02e23 # scientific notation 0xFF # hex (255) 0b1010 # binary (10) 0777 # octal (511) 1_000_000 # underscores for readability 0x1F_A0 # underscores in hex too
Arithmetic Operators
| Op | Meaning | Notes |
|---|---|---|
| + - * / | Basic arithmetic | Standard, always numeric |
| % | Modulo | Remainder after integer division |
| ** | Exponentiation | 2 ** 10 = 1024. Not ^ (that's bitwise XOR) |
| ++ -- | Auto-increment/decrement | On strings: "aa"+1 → "ab"; "Az"+1 → "Ba" |
| abs, int, sqrt | Built-in math | int truncates (not rounds) |
Comparison Operators
== converts both sides to numbers first. eq compares as strings. "foo" == 0 is TRUE (both become 0). "10" == "10.0" is TRUE. "10" eq "10.0" is FALSE.| Numeric | String | Returns |
|---|---|---|
| == | eq | Equal |
| != | ne | Not equal |
| < | lt | Less than |
| > | gt | Greater than |
| <= | le | Less than or equal |
| >= | ge | Greater than or equal |
| <=> | cmp | -1, 0, or 1 (spaceship) |
Logical Operators — Two Syntaxes
# Symbol forms — HIGH precedence (use in expressions) $a && $b # true if both are true $a || $b # true if either is true !$a # negation # Word forms — LOW precedence (use at statement level) $a and $b # same as &&, but lower precedence $a or $b # same as ||, but lower precedence not $a # same as !, but lower precedence # Defined-or (5.10+) — only checks definedness, not truth $val // "default" # use "default" if $val is undef $val //= "default" # assign default if undef # Classic idioms open my $fh, '<', $f or die $!; # or-die my $x = $input || "fallback"; # or-default
Operators are listed from highest precedence (binds tightest) to lowest. When in doubt, add parentheses — it costs nothing and aids readability.
-f file tests, chr, hex, lc, etc.print, sort, die, etc.Declaration & Basic Access
@arr. One element is $arr[i] — sigil shifts to $ because you're extracting a scalar. A slice is @arr[1,3] — sigil stays @ because you're extracting a list.my @fruits = ("apple", "banana", "cherry"); my @words = qw(foo bar baz); # quote-words shorthand my @numbers = (1..10); # range operator my @empty = (); # empty array $fruits[0] # "apple" — zero-indexed $fruits[-1] # "cherry" — last element $fruits[-2] # "banana" — second to last $#fruits # 2 — index of last element scalar @fruits # 3 — element count (also: $n = @fruits) # Slices my @first_two = @fruits[0,1]; # explicit indices my @slice = @fruits[0..1]; # range slice
Modifying Arrays
| Function | Effect | Returns |
|---|---|---|
| push @a, @vals | Append one or more values to end | New element count |
| pop @a | Remove and return last element | Removed element |
| unshift @a, @vals | Prepend one or more values to front | New element count |
| shift @a | Remove and return first element | Removed element |
| splice(@a, $off, $len, @new) | Remove $len elements at offset, insert @new | Removed elements |
| delete $a[$i] | Replace element with undef (array length unchanged) | Deleted value |
Sorting
sort @arr # alphabetical (default) sort { $a <=> $b } @arr # numeric ascending sort { $b <=> $a } @arr # numeric descending sort { lc($a) cmp lc($b) } @arr # case-insensitive string reverse sort @arr # reverse alphabetical # Schwartzian Transform: sort by expensive-to-compute key my @sorted = map { $_->[0] } # 3. strip key sort { $a->[1] <=> $b->[1] } # 2. sort by key map { [$_, compute_key($_)] } # 1. attach key @arr;
Declaration & Access
my %person = ( name => "Alice", # => is "fat comma" — auto-quotes left side age => 30, city => "Austin", ); $person{name} # "Alice" — curly braces for hash access $person{"my key"} # quoted key for non-bareword keys $person{missing} # undef (no warning by default) # Hash slice my @vals = @person{qw(name age)}; # sigil = @ for slice # Hash in list context = flattened k/v pairs my @pairs = %person; # ("name","Alice","age",30,...) — arbitrary order
Hash Functions
| Function / Op | Description |
|---|---|
| keys %h | List of all keys (arbitrary order — use sort) |
| values %h | List of all values (same order as keys) |
| each %h | Returns next (key, value) pair; use in while loop |
| exists $h{k} | True if key exists (even if value is undef) |
| defined $h{k} | True if value for key is not undef |
| delete $h{k} | Remove key/value pair; returns the deleted value |
| delete @h{@keys} | Delete a slice of keys at once |
| scalar %h | Number of key/value pairs (Perl 5.26+); older Perls: "X/Y" |
Common Hash Patterns
# Counting occurrences my %count; $count{$_}++ for @words; # Lookup set (membership test) my %is_valid = map { $_ => 1 } qw(red green blue); say "valid" if $is_valid{$color}; # Invert a hash (swap keys and values) my %reverse = reverse %original; # Merge two hashes (right-side wins on collision) my %merged = (%defaults, %overrides); # Iterate in sorted key order for my $key (sort keys %h) { printf "%-12s => %s\n", $key, $h{$key}; }
What is a Reference?
A reference is a scalar value that holds the memory address of another value — like a pointer in C. References are how you pass large data without copying, build nested structures, and store subroutines in variables.
Creating References
# \ operator: reference to an existing variable my $sref = \$scalar; # scalar ref my $aref = \@array; # array ref my $href = \%hash; # hash ref my $cref = \&mysub; # code ref # Anonymous constructors: create data directly as a ref my $aref = [1, 2, 3]; # anonymous arrayref my $href = {name => "Alice"}; # anonymous hashref my $cref = sub { $_[0] * 2 }; # anonymous sub (lambda)
Dereferencing
# Arrow notation — preferred for readability $aref->[0] # array element via ref $href->{name} # hash value via ref $cref->(@args) # call a code ref $$sref # dereference scalar ref # Block dereference — dereference to full structure @{$aref} # whole array %{$href} # whole hash @{$aref}[1,3] # slice from arrayref @{$href}{qw(a b)} # slice from hashref # Adjacent brackets: arrow is optional between brackets $aref->[0]{key} # same as $aref->[0]->{key} # ref() — identify the type of a reference ref($aref) # "ARRAY" ref($href) # "HASH" ref($cref) # "CODE" ref($obj) # "ClassName" for blessed objects ref($plain) # "" (empty string) — not a reference
Dispatch Tables
# Hash of code refs — replaces long if/elsif chains my %actions = ( add => sub { $_[0] + $_[1] }, sub => sub { $_[0] - $_[1] }, mul => sub { $_[0] * $_[1] }, ); my $result = $actions{$op}->($a, $b);
Common Patterns
# Array of Hashrefs (AoH) — the most common pattern my @people = ( { name => "Alice", age => 30 }, { name => "Bob", age => 25 }, ); $people[0]{name} # "Alice" $people[1]{age} # 25 # Hash of Arrayrefs (HoA) my %scores = ( alice => [92, 88, 95], bob => [74, 80, 65], ); $scores{alice}[0] # 92 push @{$scores{bob}}, 91; # Hash of Hashrefs (HoH) my %registry = ( alice => { email => 'a@b.com', role => 'admin' }, bob => { email => 'b@b.com', role => 'user' }, ); $registry{alice}{email} # 'a@b.com' # Array of Arrayrefs — 2D matrix my @matrix = ([1,2,3], [4,5,6], [7,8,9]); $matrix[1][2] # 6 (row 1, col 2) # Deeply nested: array of hashrefs with nested arrays my @classes = ( { name => "Math", students => [ {name => "Alice", grade => 'A'}, {name => "Bob", grade => 'B'} ], }, ); $classes[0]{students}[0]{name} # "Alice"
Data::Dumper or Devel::Dumper to print any complex structure during development: use Data::Dumper; print Dumper(\@classes);Conditionals
if ($x > 0) { say "positive"; } elsif ($x == 0) { say "zero"; } else { say "negative"; } # unless = "if not" unless ($done) { work(); }
# Postfix — condition after statement say "yes" if $flag; say "no" unless $flag; return unless defined $val; # Ternary: COND ? TRUE : FALSE my $label = $n > 0 ? "positive" : "non-positive"; # Chained ternary my $g = $s>=90 ? 'A' : $s>=80 ? 'B' : $s>=70 ? 'C' : 'F';
elsif, not else if or elif. Curly braces are always required — there is no braceless form.All Loop Forms
# while: test first, run while true while ($i < 10) { $i++ } # until: test first, run while FALSE until ($done) { work() } # do/while: always runs body at least once do { $input = <STDIN>; chomp $input; } while ($input ne 'quit'); # C-style for for (my $i=0; $i<10; $i++) { say $i } # foreach (for and foreach are identical keywords) for my $item (@list) { say $item } foreach my $i (1..10) { say $i } # $_ as implicit variable (no 'my $item' needed) for (@list) { say } # say prints $_ # Postfix for — compact one-liners say " $_" for @list; say $_*2 for 1..5; # Loop control last; # break — exit loop immediately next; # continue — skip to next iteration redo; # restart current iteration without re-evaluating condition # Labels: control outer loop from inner loop OUTER: for my $i (1..5) { for my $j (1..5) { last OUTER if $i + $j > 7; } }
Defining & Calling
# Basic subroutine — arguments arrive in @_ sub greet { my ($name, $greeting) = @_; # always unpack @_ first $greeting //= "Hello"; # default value with defined-or return "$greeting, $name!"; } greet("Alice"); # "Hello, Alice!" greet("Bob", "Hi"); # "Hi, Bob!" # Named parameters (hash style — very common) sub make_user { my (%args) = @_; my $name = $args{name} // die "name required"; my $role = $args{role} // "user"; return { name => $name, role => $role }; } make_user(name => "Alice", role => "admin"); # Multiple return values — just return a list sub minmax { my (@nums) = @_; return (min(@nums), max(@nums)); } my ($lo, $hi) = minmax(3,1,4,1,5); # Context-sensitive return with wantarray() sub context_aware { return wantarray ? (1,2,3) : "summary"; }
Argument Passing Gotchas
f(@a, @b), they merge into a single flat list in @_. Pass references instead: f(\@a, \@b). Then unpack: my ($aref, $bref) = @_;prototype feature exists but is rarely used in modern code. Named parameters via hashes is the idiomatic solution.Three Scoping Keywords
| Keyword | Type | Visibility | Lifetime |
|---|---|---|---|
| my | Lexical | Enclosing { } block only | Until block ends (or longer if closed over) |
| our | Package global | Entire package / file (and importers) | Entire program run |
| local | Dynamic | Current call stack frame (and callees) | Restored when enclosing block exits |
my $x = "outer"; { my $x = "inner"; # shadows outer — separate variable say $x; # "inner" } say $x; # "outer" # local: temporarily replaces a package variable our $sep = ","; sub with_pipe_sep { local $sep = "|"; # $sep is "|" here AND in all functions called from here print_items(); } # $sep restored to "," here
Closures
A closure is a subroutine that captures ("closes over") variables from its enclosing lexical scope. The variables live as long as the closure does — even after the outer function returns.
# Factory function: creates specialised closures sub make_counter { my ($start) = @_; my $count = $start // 0; return sub { return $count++; # $count is captured — persists }; } my $c1 = make_counter(0); my $c2 = make_counter(10); $c1->(); # 0 — each counter has its own $count $c1->(); # 1 $c2->(); # 10 # state: per-call persistent variable (no factory needed) use feature 'state'; sub auto_id { state $n = 0; # initialized once; kept across calls return ++$n; }
Operators
$str =~ /pattern/ # match — true if $str contains pattern $str !~ /pattern/ # negated match if (/pattern/) # implicit $_ =~ /pattern/ $str =~ s/old/new/ # substitution (first match) $str =~ s/old/new/g # global: replace all my $copy = $str =~ s/a/b/gr # /r: return modified copy $str =~ s/(\d+)/$1*2/ge # /e: evaluate replacement as code $str =~ tr/a-z/A-Z/ # transliterate (char mapping) my $n = ($str =~ tr/aeiou//) # count vowels (no replacement) $str =~ tr/aeiou//d # delete vowels $str =~ tr/a-z//s # /s: squeeze repeated translated chars # Compiled regex (use in multiple places) my $re = qr/\d{4}-\d{2}-\d{2}/; $str =~ $re;
Modifiers
| Flag | Meaning |
|---|---|
| /i | Case-insensitive matching |
| /g | Global — find all occurrences; in list context returns all matches |
| /m | Multiline — ^ and $ match at line boundaries, not just string |
| /s | Single-line — . also matches \n |
| /x | Extended — whitespace and #comments ignored in pattern (for readable regex) |
| /r | Return modified copy; do not modify the original string |
| /e | Evaluate replacement as Perl code (substitute only) |
| /o | Compile pattern once (optimization, rarely needed) |
Capture Groups & Variables
# Positional captures: $1, $2, ... if ($date =~ /(\d{4})-(\d{2})-(\d{2})/) { my ($year, $mon, $day) = ($1, $2, $3); } # Named captures: (?<name>...) → $+{name} $date =~ /(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/; say $+{y}, $+{m}, $+{d}; # List context /g: capture all matches at once my @words = ($text =~ /\b\w+\b/g); my @pairs = ($text =~ /(\w+)=(\w+)/g); # k,v,k,v,... # Non-capturing group: (?:...) — group without $1 /(?:foo|bar)(\d+)/ # Regex match variables $& # the entire matched string $` # everything BEFORE the match $' # everything AFTER the match $+ # text matched by the last bracket
Metacharacters Quick Reference
| Pattern | Matches |
|---|---|
| . | Any character except newline (use /s to include \n) |
| \d \D | Digit [0-9] / non-digit |
| \w \W | Word char [a-zA-Z0-9_] / non-word |
| \s \S | Whitespace [\t\n\r\f ] / non-whitespace |
| ^ $ | Start / end of string (or line in /m) |
| \A \z \Z | Absolute string start / end / end-before-optional-newline |
| \b \B | Word boundary / non-word boundary |
| * + ? | 0+, 1+, 0 or 1 (all greedy); add ? for non-greedy: *? +? ?? |
| {n} {n,} {n,m} | Exactly n, at least n, between n and m times |
| [abc] [^abc] | Character class / negated character class |
| a|b | Alternation — matches a or b |
| (?=…) (?!…) | Positive / negative lookahead (zero-width) |
| (?<=…) (?<!…) | Positive / negative lookbehind (zero-width) |
| (?>…) | Atomic group — prohibits backtracking |
map, grep, sort
These three functions process lists functionally — they never modify the original list and are the foundation of idiomatic Perl.
# map: transform each element ($_ = current element) my @doubled = map { $_ * 2 } @numbers; my @names = map { $_->{name} } @people; my %lookup = map { $_ => 1 } @keys; # build lookup hash # grep: filter (keep elements where block is true) my @evens = grep { $_ % 2 == 0 } @numbers; my @admins = grep { $_->{role} eq 'admin' } @users; my @nocomment = grep { !/^#/ } @lines; # sort: with a comparator block my @by_name = sort { $a->{name} cmp $b->{name} } @people; my @by_score = sort { $b->{score} <=> $a->{score} } @people; # Chaining — reads as a pipeline my @result = sort { $a <=> $b } grep { $_ > 5 } map { $_ ** 2 } @numbers;
List::Util Functions
| Function | Description |
|---|---|
| sum(@list) | Sum of all elements; sum0 returns 0 for empty list |
| max(@list) / min(@list) | Largest / smallest element |
| first { } @list | First element where block is true |
| any { } @list | True if block is true for any element |
| all { } @list | True if block is true for all elements |
| none { } @list | True if block is true for no elements |
| reduce { } @list | Reduce list to single value; $a and $b are accumulator/element |
| uniq(@list) | Remove duplicates (preserving order) |
| shuffle(@list) | Return list in random order |
Opening Files
# Always use three-argument open! open(my $fh, '<', 'file.txt') or die $!; # read open(my $fh, '>', 'out.txt') or die $!; # write (truncate) open(my $fh, '>>', 'log.txt') or die $!; # append open(my $fh, '+<', 'rw.txt') or die $!; # read-write # With encoding layer open(my $fh, '<:utf8', $path) or die $!; # In-memory file (open against a variable) open(my $fh, '>', \my $buf) or die $!;
Reading Files
# Line by line — most memory-efficient while (my $line = <$fh>) { chomp $line; # remove trailing \n # process $line } # Slurp all lines into an array my @lines = <$fh>; chomp @lines; # Slurp entire file into a string my $content = do { local $/; <$fh> }; # Diamond <>: reads ARGV files or STDIN while (<>) { chomp; process($_) }
File Test Operators
| Test | Meaning |
|---|---|
| -e $path | File or directory exists |
| -f $path | Is a plain file (not directory, symlink…) |
| -d $path | Is a directory |
| -l $path | Is a symbolic link |
| -r / -w / -x | Readable / writable / executable by current user |
| -s $path | File size in bytes (0 if empty) |
| -z $path | File is empty (zero size) |
| -T / -B | Text file / Binary file (heuristic) |
| -M / -A / -C | Age in days: last modified / accessed / inode changed |
Directory Operations
opendir(my $dh, ".") or die $!; my @files = grep { !/^\./ } readdir($dh); # exclude dotfiles closedir($dh); use File::Find; # recursive directory walk use File::Glob; # glob patterns use File::Path qw(make_path remove_tree); # mkdir -p / rm -rf use File::Basename qw(dirname basename); # path components use File::Spec; # portable path manipulation
die, warn, eval
# die: throw exception (exits program if not caught) die "Something failed\n"; # \n suppresses "at line N" die "Error: $!\n"; # $! = system error message die { type=>"NotFound", msg=>"..." }; # structured exception object # warn: print warning to STDERR, continue execution warn "Something looks wrong\n"; # eval { }: catch exceptions (Perl's try block) my $result = eval { risky_operation(); "success"; # return value if no exception }; if (my $e = $@) { # $@ holds the caught exception if (ref($e) eq 'HASH') { say "Caught: $e{type}: $e{msg}"; } else { die $e; # re-throw unknown exceptions } } # Carp module: better caller-perspective error messages use Carp qw(carp croak confess cluck); croak "bad input"; # like die, but blame the caller carp "suspicious"; # like warn, blame the caller confess "deep error"; # die + full stack trace cluck "soft error"; # warn + full stack trace
Exception Objects with Exception::Class
use Exception::Class ( 'MyApp::Error' => { description => 'Base error' }, 'MyApp::Error::IO' => { isa => 'MyApp::Error', fields => ['filename'] }, ); eval { MyApp::Error::IO->throw( message => "Cannot open file", filename => $path, ); }; if (my $e = $@) { if ($e->isa('MyApp::Error::IO')) { say "IO error on " . $e->filename; } }
The Three Primitives
bless()) is an object. A subroutine in the package is a method — called with $obj->method(). That's the entire object system. Everything else is convention built on these three rules.package Animal; use strict; use warnings; # Constructor — 'new' is conventional, any name works sub new { my ($class, %args) = @_; # $class = "Animal" (the package name) my $self = { name => $args{name} // "Unknown", sound => $args{sound} // "...", }; return bless $self, $class; # bless ties $self to class } # Accessor method (get/set) sub name { my ($self, $new) = @_; $self->{name} = $new if defined $new; return $self->{name}; } # Regular method sub speak { my ($self) = @_; printf "%s says %s\n", $self->{name}, $self->{sound}; } # Subclass package Dog; use parent 'Animal'; # inherit from Animal sub new { my ($class, %args) = @_; $args{sound} //= "Woof"; return $class->SUPER::new(%args); # call parent constructor } package main; my $dog = Dog->new(name => "Rex"); $dog->speak(); # "Rex says Woof" ref($dog); # "Dog" $dog->isa('Animal'); # 1 (true) $dog->can('speak'); # coderef or undef
Modern OOP: Moose & Moo
package Person; use Moo; has 'name' => (is => 'rw', required => 1); has 'email' => (is => 'rw'); has 'age' => (is => 'ro', default => 0); sub greet { my ($self) = @_; say "Hi, I'm " . $self->name; } # Moo automatically generates constructor, accessors, type checking
Key OOP Concepts Summary
| Concept | Traditional Perl | Moose / Moo |
|---|---|---|
| Class | package Foo; | package Foo; use Moose; |
| Constructor | sub new { bless {}, $class } | Auto-generated |
| Attributes | Hash keys in $self | has 'attr' => (is=>'rw'); |
| Inheritance | use parent 'Base'; | extends 'Base'; |
| Mixins/Roles | Multiple inheritance | with 'Role::Name'; |
| Method override | sub foo { ... SUPER::foo ... } | around 'foo' => sub { ... }; |
| Type check | Manual validation | isa => 'Int' or Types::Standard |
| Destruction | sub DESTROY { ... } | DEMOLISH |
use vs require
| use | require | |
|---|---|---|
| When executed | Compile time (early) | Runtime (when line reached) |
| Import | Calls import() automatically | Does not call import() |
| Version check | use 5.036; works | Only for modules |
| Typical use | Almost always | Conditional loading |
use List::Util qw(sum max min first); # import specific subs use Scalar::Util (); # load but don't import Scalar::Util::blessed($obj); # call fully-qualified use POSIX qw(floor ceil strftime);
Writing a Module
package MyUtil; use strict; use warnings; our $VERSION = '1.00'; use Exporter 'import'; our @EXPORT_OK = qw(util_one util_two); # opt-in our @EXPORT = qw(); # auto-export (avoid unless simple) our %EXPORT_TAGS = (all => [@EXPORT_OK]); sub util_one { ... } sub util_two { ... } 1; # MODULE MUST RETURN TRUE — never forget this!
Essential CPAN Modules by Category
| Module | Category | Purpose |
|---|---|---|
| Moose / Moo | OOP | Powerful, declarative object system |
| DBI + DBD::* | Database | Universal database interface |
| Mojolicious | Web | Full-stack web framework (no deps) |
| Dancer2 | Web | Lightweight sinatra-style web framework |
| LWP::UserAgent | HTTP | HTTP client (make web requests) |
| HTTP::Tiny | HTTP | Lightweight HTTP client (core module) |
| JSON / JSON::XS | Data | JSON encode/decode |
| YAML::PP | Data | YAML parse/generate |
| Text::CSV_XS | Data | Robust CSV parsing |
| DateTime | Time | Comprehensive date/time manipulation |
| Path::Tiny | Files | Elegant file/path operations |
| Getopt::Long | CLI | Command-line option parsing |
| Template (TT) | Templates | Powerful template engine |
| Try::Tiny | Errors | Simple, correct try/catch/finally |
| Test::More | Testing | Standard testing harness |
| Carp | Errors | Better error context (core) |
| Storable | Serialize | Deep copy, serialization (core) |
| Data::Dumper | Debug | Pretty-print any data structure (core) |
cpanm Module::Name # cpanminus — recommended installer cpan Module::Name # built-in CPAN shell apt install libfoo-perl # system package manager (Debian/Ubuntu) brew install cpanminus # macOS via Homebrew
Most Commonly Used
| Variable | English name | Meaning |
|---|---|---|
| $_ | $ARG | Default variable for loops, print, match, chomp, etc. |
| @_ | Subroutine argument list — always unpack at top of sub | |
| $! | $OS_ERROR | System error from last failed OS call (as string or number) |
| $@ | $EVAL_ERROR | Exception caught by last eval block |
| $? | $CHILD_ERROR | Exit status of last system() or backtick command |
| $0 | $PROGRAM_NAME | Name of the running script |
| @ARGV | Command-line arguments | |
| %ENV | Environment variables (read/write) | |
| $/ | $INPUT_RECORD_SEP | Input record separator (default: \n); set to undef to slurp |
| $\ | $OUTPUT_RECORD_SEP | Appended to every print statement |
| $, | $OUTPUT_FIELD_SEP | Separator between print arguments |
| $" | $LIST_SEP | Separator used when array interpolated in string (default: space) |
| $. | $INPUT_LINE_NUMBER | Current line number of last filehandle read |
| $; | $SUBSCRIPT_SEP | Multi-key hash subscript separator (rare) |
| $& | $MATCH | Entire string matched by last regex |
| $1..$9 | Captured groups from last successful regex match | |
| %+ | Named captures from last regex: $+{name} | |
| $^W | $WARNING | True if warnings enabled (prefer use warnings) |
| $^O | $OSNAME | Operating system name: "linux", "darwin", "MSWin32" |
| $^T | $BASETIME | Time (epoch) when program started |
use English; to get readable aliases for punctuation variables. Example: $OS_ERROR instead of $!. However, this has a small performance cost for regex-related variables, so some skip it in tight loops.Flags
| Flag | Effect |
|---|---|
| -e 'code' | Execute code string directly (no .pl file needed) |
| -n | Wrap code in while (<>) { }; reads lines, sets $_; no automatic print |
| -p | Like -n but prints $_ after each iteration (like sed) |
| -i[ext] | Edit files in-place; optional extension creates backup: -i.bak |
| -a | Auto-split $_ on whitespace into @F (use with -n/-p) |
| -F/pat/ | Set split pattern for -a (instead of whitespace) |
| -l | Auto-chomp input lines; append $/ to print output |
| -0[oct] | Set $/ to given octal value (0 = null, 777 = slurp whole file) |
| -c | Check syntax only; don't execute |
| -w | Enable warnings (use use warnings in scripts instead) |
| -d | Run under debugger |
| -de 42 | Start interactive debugger (Perl REPL) |
| -M Module | Load a module before executing: perl -MList::Util=sum -e 'say sum(1..10)' |
One-Liner Recipes
# Print lines matching a pattern perl -ne 'print if /ERROR/' server.log # Replace text in-place across multiple files perl -pi.bak -e 's/\bfoo\b/bar/g' *.txt # Print only lines 15-17 perl -ne 'print if $. >= 15; last if $. >= 17' file.txt # Sum a column of numbers (2nd field) perl -ane '$sum += $F[1]; END { say $sum }' data.txt # Remove duplicate lines (preserving order) perl -ne 'print unless $seen{$_}++' file.txt # Count occurrences of each word perl -ne 'for (split){ $c{$_}++ } END{ say "$_ $c{$_}" for sort keys %c }' file.txt # Reverse each line perl -lpe '$_ = reverse' file.txt # Rename files: strip .txt from all *.txt.bak files perl -e 'rename $_, s/\.txt//r for glob "*.txt.bak"' # Pretty-print JSON (requires JSON::PP) perl -MJSON::PP -e 'print JSON::PP->new->pretty->encode(decode_json(do{local $/;<STDIN>}))' # Find and print palindromes in a word list perl -lne 'print if lc eq reverse lc' /usr/share/dict/words
Operator Overloading
package Vector; use overload '+' => \&add, '""' => \&stringify; # overload stringification sub new { bless { x => $_[1], y => $_[2] }, $_[0] } sub add { Vector->new($_[0]{x}+$_[1]{x}, $_[0]{y}+$_[1]{y}) } sub stringify { "($_[0]{x}, $_[0]{y})" } my $v = Vector->new(1,2) + Vector->new(3,4); say $v; # "(4, 6)"
Formats & Context Summary
| Context | How triggered | Effect |
|---|---|---|
| Scalar | my $n = @arr | Array returns element count; localtime returns formatted string |
| List | my @copy = @arr, my ($a,$b) = func() | Array/hash expands to elements; functions return full list |
| Boolean | if (@arr) | Undef/0/"0"/"" are false; everything else is true |
| Void | func(); (ignoring return) | Function may optimize by not building return value |
| Numeric | $s + 0 | String converted to number; non-numeric string → 0 + warning |
| String | $n . "" | Number converted to string representation |
Advanced Features Overview
Code Style (from Perl Best Practices)
| Category | Rule |
|---|---|
| Safety | Always use strict; use warnings; — no exceptions |
| Safety | Always unpack @_ explicitly at the start of every subroutine |
| Safety | Always use 3-argument open() and check the return value |
| Safety | Never use bareword filehandles |
| Safety | Avoid symbolic references entirely |
| Clarity | Use my for every variable; minimize scope |
| Clarity | Use //= not ||= when 0 or "" are valid values |
| Clarity | Prefer named variables over $_ when the name aids clarity |
| Clarity | Use elsif, not cascaded if checks on the same variable |
| Naming | Variables: $my_variable (snake_case for scalars/arrays/hashes) |
| Naming | Constants: MAX_RETRIES (ALL_CAPS) |
| Naming | Packages/Classes: MyApp::Parser (CamelCase) |
| Naming | Private subs: prefix with underscore _helper() |
| Layout | 4-space indentation; 78-column line limit |
| Layout | Trailing comma on last element of multiline list |
| Layout | Align corresponding items vertically |
| Subroutines | Use named parameters (hash) for 3+ arguments |
| Subroutines | Always use explicit return |
| Error handling | Throw exceptions (die) instead of returning error flags |
| Modules | Export on request (@EXPORT_OK), not automatically |
| OOP | Don't use indirect object syntax: new Foo() → use Foo->new() |
| Performance | Don't optimize without profiling (use Devel::NYTProf) |
| Testing | Write tests first (Test::More, Test::Exception) |
Modern Perl Style
# Modern preamble (Perl 5.36+) use v5.36; # enables strict, warnings, say, state, and more use utf8; use feature 'signatures'; # named params in subs no warnings 'experimental::signatures'; # Named sub parameters (Perl 5.20+ with signatures) sub greet ($name, $greeting = "Hello") { say "$greeting, $name!"; } # Try::Tiny for clean exception handling use Try::Tiny; try { risky_op(); } catch { warn "Error: $_"; } finally { cleanup(); };
Stage 1 — Foundations (Weeks 1-2)
Project: Write a script that reads a CSV, processes it, and writes a summary report.
Stage 2 — Intermediate (Weeks 3-5)
Project: Build a log-file analyzer that parses Apache logs, categorizes errors, and generates an HTML report using regex and data structures.
Stage 3 — Object-Oriented (Weeks 6-8)
Project: Build a small ORM layer that wraps DBI with objects representing database rows.
Stage 4 — Advanced Mastery (Ongoing)
Canonical Books (Your Bookshelf)
| Book | Level | Best for |
|---|---|---|
| Learning Perl (Llama book) | Beginner | First Perl book; covers all fundamentals clearly |
| Intermediate Perl (Alpaca book) | Intermediate | References, OOP, modules, testing |
| Programming Perl (Camel book) | Reference | The definitive comprehensive reference |
| Modern Perl | Intermediate+ | Current idioms, Moose, CPAN best practices |
| Perl Best Practices | Intermediate | Code quality, style, naming conventions |
| Mastering Algorithms with Perl | Advanced | CS algorithms implemented in Perl |
Key Online Resources
| Resource | URL |
|---|---|
| Official documentation | perldoc.perl.org |
| CPAN module search | metacpan.org |
| Modern Perl book (free) | modernperlbooks.com |
| PerlMonks forum | perlmonks.org |
| Perl Weekly newsletter | perlweekly.com |
| Learn Perl in ~2h | learn.perl.org |