Cheatsheet Perl
Linguagem de scripting poderosa para texto, sysadmin e automação
Perl
Basic and Variables
Program Structure
#!/usr/bin/env perl use strict; use warnings; use v5.36; # My first script say "Hello World!"; my $name = "Anna"; say "Hello, $name!";
A Perl script starts with the shebang #!/usr/bin/env perl. The pragmas use strict and use warnings are mandatory in modern code. say prints with an automatic newline.
Strings and Operators
my $s = "Hello" . " World"; # concatenation
my $rep = "ab" x 3; # "ababab"
my $len = length($s); # 11
uc("text"); # "TEXT"
lc("TEXT"); # "text"
ucfirst("name"); # "Name"
substr($s, 0, 5); # "Hello"
index($s, "World"); # positionThe . operator concatenates strings and x repeats. Functions like uc, lc, substr and index manipulate text. length returns the size. Double quotes interpolate variables; single quotes are literal.
Special Operators (// and or)
# Defined-or (//): my $val = $input // "default"; # uses $input if defined (even 0 or "") # Low-precedence or: open(my $fh, "<", $file) or die "Error: $!"; # Ternary: my $status = $age >= 18 ? "adult" : "minor"; # Auto-increment on string: my $col = "A"; $col++; # "B", ..., "Z", "AA"...
The // operator (defined-or) returns the right operand only if the left one is undef — unlike || which also treats 0 and "" the false. or die is the idiomatic error handling pattern.
Scalar Variables ($)
my $name = "Anna"; my $age = 30; my $pi = 3.14159; my $active = 1; # true my $empty = undef; # no value # Interpolation in double quotes: print "Hello, $name! You are $age years old.\n"; # Concatenation with dot: my $msg = $name . " is " . $age;
Scalars ($) hold a single value: string, number or undef. Perl converts automatically depending on the operator — . concatenates, + adds. my declares lexical variables scoped to the block.
qw and Here-doc
# qw (quote words) - list of words:
my @colors = qw(red green blue);
# same the ("red", "green", "blue")
# Here-doc (multi-line string):
my $text = <<END;
Line 1
Line 2 with $name
END
# sprintf (formatting):
my $fmt = sprintf("%.2f", 3.14159); # "3.14"qw() creates lists of words without quotes or commas. The here-doc <<END allows multi-line strings with interpolation. sprintf formats numbers and strings with full precision control.
Pragmas
use strict; # requires variable declaration use warnings; # warns about problems use v5.36; # modern version (includes both) use utf8; # source code in UTF-8 use feature 'say'; # say = print + \n # With use v5.36+: use v5.36; say "Hello without manual \n"; # Disable temporarily: no warnings 'numeric'; my $n = "12abc" + 0; # no warning
use strict prevents undeclared variables (typos become errors). use warnings alerts about suspicious operations. use v5.36 enables both plus modern features like say. These pragmas turn silent bugs into detectable errors.
Arrays (@)
my @fruits = ("apple", "banana", "grape");
my @nums = (1..10); # range 1 to 10
# Access (index starts at 0):
my $first = $fruits[0]; # "apple"
my $last = $fruits[-1]; # "grape"
# Size:
my $size = scalar @fruits; # 3Arrays (@) are ordered lists with indices starting at 0. The sigil changes: @fruits for the list, $fruits[0] for one element. Negative indices count from the end (-1 is the last).
Context
my @list = (1, 2, 3); # Scalar vs list context: my $size = @list; # 3 (scalar) my @copy = @list; # (1,2,3) (list) # List assignment: my ($a, $b, $c) = (10, 20, 30); my ($name, $age) = split(/,/, "Anna,30");
The concept of context is central in Perl: the same expression behaves differently in scalar vs list context. An array in scalar context returns its size; in list context it returns the elements.
undef and defined
my $x = undef; # no value
# Check if defined:
if (defined $x) {
say "Has a value";
}
# Set with defined-or:
$x //= 42; # only if $x is undef
# undef the a function:
undef $x; # clears the variable
my @arr = ();
undef @arr; # empties the arrayundef represents the absence of a value (like null). defined checks whether a variable has a value. The //= operator assigns only if it is undef. Do not confuse undef with 0 or an empty string — those are defined values.
Hashes (%)
my %person = (
name => "Anna",
age => 30,
city => "Lisbon",
);
# Access:
my $name = $person{name};
$person{email} = "ana@mail.com";
# Keys and values:
my @keys = keys %person;
my @vals = values %person;Hashes (%) are key/value dictionaries. The syntax key => value is sugar for ("key", "value"). keys returns the keys and values the values. Access is done with braces: $person{name}.
Diamond Operator and chomp
# Read lines from files/STDIN:
while (my $line = <>) {
chomp $line; # removes trailing \n
print $line;
}
# chomp vs chop:
my $s = "Hi\n";
chomp $s; # removes only the trailing \n
chop $s; # removes the last characterThe diamond operator <> reads lines from the files passed the arguments or from STDIN. chomp removes the trailing newline (essential when reading input). chop removes the last character, whatever it is.
say vs print
# print (no automatic newline): print "Hello"; print "World\n"; # manual \n # say (with newline, Perl 5.10+): say "Hello"; # prints "Hello\n" say "World"; # print with filehandle: print $fh "line\n"; # say to stderr: say STDERR "Error!";
say is like print but adds a newline automatically (requires use feature 'say' or use v5.36). print is more flexible for output without a line break. Both accept a filehandle the the first argument.
Subroutines and Functions
Basic Subroutine
# Definition:
sub greeting {
my ($name, $time) = @_;
return "Hello, $name! ($time)";
}
# Call:
my $msg = greeting("Anna", "morning");
say $msg;
# Without parentheses (idiomatic):
greeting "Anna", "afternoon";Subroutines are defined with sub. Arguments arrive via @_ and the pattern my ($a, $b) = @_ does destructuring. return returns the value (the last expression is the implicit return).
Closures
sub make_counter {
my $count = 0; # private lexical variable
return sub {
$count++;
return $count;
};
}
my $c = make_counter();
say $c->(); # 1
say $c->(); # 2 (count persists!)
# Each counter is independent:
my $d = make_counter();
say $d->(); # 1Closures capture lexical variables from the scope where they were created — $count "lives" inside the anonymous sub even after make_counter() returns. Each call produces an independent counter.
sort
my @nums = (3, 1, 4, 1, 5);
# Numeric ascending:
my @asc = sort { $a <=> $b } @nums;
# Numeric descending:
my @desc = sort { $b <=> $a } @nums;
# Alphabetic:
my @alpha = sort { $a cmp $b } @strings;
# By hash field:
my @by_age = sort {
$a->{age} <=> $b->{age}
} @people;sort sorts with a custom comparator. $a and $b are the two compared elements; the block returns negative, zero or positive. Use <=> for numbers and cmp for strings.
@_ and Parameters
# @_ contains all the arguments:
sub demo {
say "Total: " . scalar @_;
say "First: $_[0]";
say "Last: $_[-1]";
}
# Destructuring with defaults:
sub connect {
my ($host, $port) = @_;
$port //= 3306; # default value
}@_ is the special array with the passed arguments. $_[0] is the first, $_[-1] the last. The //= operator sets default values for optional parameters.
state
use feature 'state';
sub counter {
state $count = 0; # persists between calls
return ++$count;
}
say counter(); # 1
say counter(); # 2
say counter(); # 3state (Perl 5.10+) creates variables that persist between calls without being global. Initialization happens only on the first call. It is ideal for counters and private caches.
reduce and List::Util
use List::Util qw(reduce sum max min first);
my @nums = (3, 1, 4, 1, 5, 9);
my $sum = sum @nums; # 23
my $highest = max @nums; # 9
my $lowest = min @nums; # 1
# reduce (generic accumulator):
my $total = reduce { $a + $b } @nums;
# first (first that satisfies):
my $x = first { $_ > 4 } @nums; # 5List::Util (core) eliminates manual loops: sum, max, min do aggregations; first returns the first element that satisfies the test; reduce is the generic accumulator for any binary operation.
Options Hash (named params)
sub create_user {
my %opts = @_;
my $name = $opts{name} // die "name required";
my $email = $opts{email};
my $admin = $opts{admin} // 0;
say "$name ($email)";
}
# Self-documenting call:
create_user(name => "Anna", admin => 1);The options hash pattern (%opts = @_) simulates named parameters: the call becomes self-documenting (name => "Anna") and the order does not matter. It is the preferred pattern for functions with many parameters.
map
# Transform each element:
my @doubles = map { $_ * 2 } (1..5);
# (2, 4, 6, 8, 10)
# Extract a field from hashes:
my @names = map { $_->{name} } @people;
# Build a hash:
my %by_id = map { $_->{id} => $_ } @records;
# $_ is the current elementmap transforms each element of a list, and can produce 0, 1 or N results per input. $_ is the current element. It is ideal for extracting fields from hashes or building new structures.
Recursion
sub factorial {
my ($n) = @_;
return 1 if $n <= 1;
return $n * factorial($n - 1);
}
say factorial(5); # 120
sub fibonacci {
my ($n) = @_;
return $n if $n < 2;
return fibonacci($n-1) + fibonacci($n-2);
}Perl supports recursion naturally. Every recursive function needs a base case (return 1 if $n <= 1) to stop. For deep recursion, prefer iteration — Perl does not optimize tail-calls.
Anonymous Sub and Code Refs
# Anonymous sub:
my $double = sub { $_[0] * 2 };
say $double->(21); # 42
# Code ref the a parameter:
sub apply {
my ($fn, $value) = @_;
return $fn->($value);
}
say apply(sub { $_[0] + 1 }, 41); # 42
# Reference to a named sub:
my $ref = \&greeting;
$ref->("Anna", "night");Anonymous subs (sub { }) are first-class values, stored in scalars and passed the arguments. They are called with the arrow ->(). \&name creates a reference to a named sub.
grep
# Filter elements:
my @adults = grep { $_ > 18 } @ages;
my @active = grep { $_->{active} } @users;
# In boolean context (check existence):
if (grep { $_ > 100 } @values) {
say "There are large values";
}
# $_ is the tested elementgrep filters a list, returning only the elements that pass the test (the block returns true). $_ is the tested element. In boolean context it checks whether any element satisfies the condition.
Memoization
use Memoize;
memoize('fibonacci');
sub fibonacci {
my ($n) = @_;
return $n if $n < 2;
return fibonacci($n-1) + fibonacci($n-2);
}
# Manual memoization:
my %cache;
sub fib {
my ($n) = @_;
return $cache{$n} //= ($n < 2 ? $n
: fib($n-1) + fib($n-2));
}Memoization caches the results of pure functions — fibonacci(40) goes from minutes to instant. The Memoize module does this automatically; the manual version uses a hash with //=.
Advanced
eval (native try/catch)
# eval catches die:
my $result = eval {
die "Something failed!\n" if $error;
calculate();
};
if ($@) {
warn "Caught error: $@";
}
# $@ contains the die message
# eval returns undef if there is an errorPerl uses eval { } + die the the native exception mechanism. eval catches the nearest die and stores the message in $@. If there is no error, $@ stays empty. It is the foundation of exception handling in Perl.
CPAN and cpanm
# Install modules: # cpanm Module::Name (App::cpanminus) # cpan Module::Name (classic client) # Essential (core) modules: use Getopt::Long; # command-line args use File::Basename; # basename/dirname use POSIX qw(strftime ceil); use Time::Piece; # dates # Popular CPAN modules: use DBI; # databases use LWP::UserAgent; # HTTP use Mojolicious; # web framework
CPAN has 200,000+ modules; cpanm installs them easily. Core modules ship with Perl (Getopt::Long, POSIX, Time::Piece). DBI is as universal database interface; LWP makes HTTP requests; Mojolicious is a web framework.
Tied variables
# Tie a hash to a file (persistence):
use DB_File;
tie my %db, 'DB_File', "data.db"
or die "Cannot open: $!";
$db{key} = "value"; # persists!
untie %db;
# Tie::File (file the an array):
use Tie::File;
tie my @lines, 'Tie::File', "f.txt";
$lines[0] = "New first line";
untie @lines;tie binds a variable to a class that intercepts every operation — the variable looks normal but the behavior is custom. DB_File persists hashes on disk; Tie::File treats a file the an array (each line is an element).
Try::Tiny
use Try::Tiny;
try {
dangerous_operation();
} catch {
warn "Error: $_"; # $_ holds the message
} finally {
cleanup();
};
# Safer than eval/$@:
# fixes subtle $@ localization issuesTry::Tiny offers clean try/catch/finally syntax and fixes subtle eval/$@ issues (localization, objects). The error message is in $_ inside the catch. It is the recommended way to handle exceptions in modern Perl.
Getopt::Long
use Getopt::Long;
my $verbose = 0;
my $output = "out.txt";
my @includes;
GetOptions(
"verbose|v" => \$verbose,
"output|o=s" => \$output,
"include|I=s" => \@includes,
) or die "Usage: $0 [-v] [-o file]\n";
# perl script.pl -v -o res.txt -I a -I b
say "Output: $output" if $verbose;Getopt::Long parses command-line arguments: =s expects a string, |v creates a short alias. It fills variables by reference. It is the standard for scripts with options (--verbose, --output=file).
Data::Dumper
use Data::Dumper;
my $data = {
users => [ { name => "Anna" }, { name => "Ray" } ],
total => 2,
};
# Inspection for debugging:
print Dumper($data);
# Useful configuration:
$Data::Dumper::Sortkeys = 1; # sort keys
$Data::Dumper::Indent = 1; # simple indentationData::Dumper prints the full structure of any reference in readable Perl format — indispensable for debugging complex structures. Sortkeys sorts the keys for consistent output.
Carp
use Carp qw(croak carp confess cluck);
# croak = die at the CALLER line:
sub divide {
my ($a, $b) = @_;
croak "Division by zero" if $b == 0;
}
# carp = warn at the caller line:
carp "Suspicious value";
# confess = die with full stack trace:
confess "Serious error";Carp improves error messages in modules: croak reports the error at the caller line (not the module), carp does the same for warnings, confess includes the full stack trace. Essential for production code.
fork (processes)
my $pid = fork();
if ($pid == 0) {
# child process
heavy_work();
exit 0;
} elsif ($pid > 0) {
# parent process
waitpid($pid, 0); # waits for the child
say "Child finished";
} else {
die "fork failed: $!";
}fork() creates a child process with independent memory (no race conditions). It returns 0 in the child and the PID in the parent. waitpid waits for the child to finish. It is the most natural and safe concurrency model in Perl.
JSON
use JSON;
my $data = { name => "Anna", age => 30 };
# Serialize:
my $json = encode_json($data);
# {"name":"Anna","age":30}
# Deserialize:
my $obj = decode_json($json);
say $obj->{name}; # Anna
# Pretty print:
my $pretty = JSON->new->pretty->encode($data);The JSON module converts between Perl structures and JSON: encode_json serializes, decode_json deserializes. Hashes become JSON objects and arrays become lists. pretty formats with indentation for reading.
use vs require
# use (compile-time + import):
use JSON;
use List::Util qw(sum max);
# require (runtime, no import):
require JSON;
JSON->import; # manual import
# conditional require:
if ($debug) {
require Data::Dumper;
Data::Dumper->import;
}
# use is evaluated before executionuse loads and imports at compile time (errors stop compilation); require loads at runtime (for optional modules). use Module qw(...) is equivalent to require + import. Use require for conditional dependencies.
Parallel::ForkManager
use Parallel::ForkManager;
my $pm = Parallel::ForkManager->new(4); # 4 workers
for my $url (@urls) {
$pm->start and next; # fork (parent skips)
download($url); # child only
$pm->finish; # child exit
}
$pm->wait_all_children;
say "All done";Parallel::ForkManager simplifies worker pools with a process limit. $pm->start forks (the parent skips with and next); the child runs and calls $pm->finish. Ideal for processing many tasks in parallel.
DateTime
use DateTime; my $dt = DateTime->now; say $dt->ymd; # 2024-01-15 say $dt->hms; # 14:30:00 say $dt->year; # Arithmetic: $dt->add(days => 7); my $future = $dt->clone->add(months => 1); # Difference: my $d = DateTime->new(year => 2024, month => 3, day => 1); my $diff = $d->delta_days($dt);
DateTime (CPAN) is the robust way to work with dates: ymd/hms format, add adds time, clone avoids mutation. delta_days computes differences. Far superior to localtime for date calculations.
Arrays e Hashes
push / pop / shift / unshift
my @queue = (1, 2, 3); push @queue, 4; # adds at the end → (1,2,3,4) pop @queue; # removes from the end → (1,2,3) unshift @queue, 0; # adds at the start → (0,1,2,3) shift @queue; # removes from the start → (1,2,3) # push multiple: push @queue, (5, 6, 7);
push/pop manipulate the end of the array; unshift/shift manipulate the start. push and unshift accept several elements. shift is widely used to take the first argument in subroutines.
each / keys / values
my %person = (name => "Anna", age => 30);
# Iterate pair by pair:
while (my ($k, $v) = each %person) {
say "$k: $v";
}
# Only keys / only values:
for my $k (keys %person) {
say "$k => $person{$k}";
}
my @vals = values %person;
# exists / delete:
exists $person{name}; # True
delete $person{age}; # removeseach iterates pair by pair (more efficient on large hashes). keys and values return lists. exists checks whether a key exists (even with an undef value); delete removes the entry.
Hash Merge
my %a = (x => 1, y => 2);
my %b = (y => 3, z => 4);
# Merge (b takes precedence):
my %merged = (%a, %b); # x=1, y=3, z=4
# Merge without overwriting:
my %safe = (%b, %a); # y stays 2
# Add only new keys:
for my $k (keys %b) {
$a{$k} //= $b{$k};
}Hash merging is done with (%a, %b) — for duplicate keys, the second hash wins. To avoid overwriting, reverse the order or use //= to fill only missing keys.
splice
my @arr = (1, 2, 5, 6); # Insert in the middle: splice @arr, 2, 0, (3, 4); # (1,2,3,4,5,6) # Remove elements: splice @arr, 1, 2; # removes 2 elems # Replace: splice @arr, 0, 1, (9, 9); # swaps the first # splice @arr, offset, length, @replacement
splice is the most versatile array operation: it inserts, removes and replaces elements at any position. The syntax is splice @arr, offset, length, @replacement. With length 0 it only inserts; without a replacement it only removes.
exists vs defined
my %h = (a => 1, b => undef);
exists $h{a}; # True (key exists)
exists $h{b}; # True (key exists, value undef)
exists $h{c}; # False (key does not exist)
defined $h{a}; # True (non-undef value)
defined $h{b}; # False (value is undef)
defined $h{c}; # False
# delete vs undef:
delete $h{b}; # removes the key
$h{b} = undef; # key stays, value undefexists checks whether the KEY exists; defined checks whether the VALUE is not undef. A key can exist with an undef value (exists true, defined false). delete removes the key; assigning undef keeps it.
Sort Hash by Value
my %grades = (Anna => 15, Ray => 18, Mia => 12);
# Keys sorted by value (desc):
my @top = sort { $grades{$b} <=> $grades{$a} } keys %grades;
for my $name (@top) {
say "$name: $grades{$name}";
}
# Ray: 18, Anna: 15, Mia: 12To sort a hash by value, sort the keys comparing $hash{$b} <=> $hash{$a} (descending) or $hash{$a} <=> $hash{$b} (ascending). The result is a list of sorted keys.
slice
my @fruits = qw(apple banana grape pear kiwi);
# Array slice:
my @sub = @fruits[0, 2, 4]; # (apple, grape, kiwi)
# Hash slice:
my %h = (a => 1, b => 2, c => 3);
my @vals = @h{qw(a c)}; # (1, 3)
# Assignment via slice:
@h{qw(x y)} = (10, 20);Slices access several elements at once. Note the sigil: @fruits[0,2] (array slice uses @) vs $fruits[0] (one element uses $). Hash slices use @h{qw(...)}.
Hash of Hashes
my %data;
$data{Anna}{age} = 30;
$data{Anna}{city} = "Lisbon";
$data{Ray}{age} = 25;
# Access:
say $data{Anna}{city}; # Lisbon
# Iterate:
for my $name (keys %data) {
my $info = $data{$name};
say "$name: $info->{age} years old";
}Hashes of hashes are the most common structure for structured data in Perl. The syntax $data{Anna}{age} is autovivifying: it creates the intermediate hashes automatically upon assignment.
List::MoreUtils
use List::MoreUtils qw(uniq any all none mesh);
my @nums = (3, 1, 4, 1, 5, 9, 2, 6);
my @unique = uniq @nums; # removes duplicates
any { $_ > 8 } @nums; # True (some > 8)
all { $_ > 0 } @nums; # True (all > 0)
none { $_ < 0 } @nums; # True (none < 0)
# mesh (interleave):
my @a = (1, 2, 3);
my @b = qw(a b c);
my @m = mesh @a, @b; # (1,a,2,b,3,c)List::MoreUtils (CPAN) extends List::Util: uniq removes duplicates, any/all/none test conditions, mesh interleaves lists. They complement the native higher-order functions.
join and split
my @arr = (1, 2, 3, 4);
# join (list → string):
my $csv = join(",", @arr); # "1,2,3,4"
my $txt = join(" - ", @arr); # "1 - 2 - 3 - 4"
# split (string → list):
my @parts = split(/,/, $csv); # (1,2,3,4)
my @words = split(/\s+/, $line); # by whitespace
# split with a limit:
my @top3 = split(/,/, $csv, 3);join builds a string from a list with a separator. split does the opposite: it splits a string into a list by a pattern (regex). They are inverse operations and widely used for CSV and parsing.
Hash of Arrays
my %classes;
push @{$classes{A}}, "Anna", "Brian";
push @{$classes{B}}, "Carlos";
# Access:
say $classes{A}[0]; # Anna
# Iterate:
for my $class (keys %classes) {
say "Class $class:";
say " $_" for @{$classes{$class}};
}Hashes of arrays group lists by key. push @{$classes{A}} adds to the array of key A (the reference is dereferenced with @{ }). Access combines hash and array: $classes{A}[0].
Multidimensional Arrays
# 2D matrix (array of arrays):
my @matrix = (
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
);
# Access:
say $matrix[1][2]; # 6
# Traverse:
for my $row (@matrix) {
for my $cell (@$row) {
print "$cell ";
}
say "";
}Perl has no native multidimensional arrays — arrays of array references are used. Each row is a [...] reference. Access chains indices: $matrix[1][2]. @$row dereferences the row.
Flow Control
if / elsif / else
my $grade = 15;
if ($grade >= 18) {
say "Excellent";
} elsif ($grade >= 10) {
say "Passed";
} else {
say "Failed";
}The if/elsif/else structure tests conditions in order. elsif (not elseif) chains multiple conditions. Parentheses in the condition are optional but recommended.
while / until
# while (while true):
while (my $line = <$fh>) {
chomp $line;
process($line);
}
# until (while false):
until ($done) {
$done = attempt();
}
# Infinite loop:
while (1) {
last if quit();
}while repeats while the condition is true; until repeats while it is false (the complement). while (<$fh>) is the classic pattern for line-by-line file reading.
Numeric vs String Comparison
# Numeric:
== != < > <= >=
# Strings (different!):
eq ne lt gt le ge
# The classic bug:
"10" == "9" # FALSE (numeric: 10 != 9)
"10" lt "9" # TRUE (string: "1" < "9")
# Always use the right operator:
if ($age >= 18) { } # numeric
if ($name eq "Anna") { } # stringPerl separates numeric operators (==, <) from string operators (eq, lt). Using the wrong one is a classic bug: "10" lt "9" is true because it compares lexicographically. Use numeric operators for numbers and eq/lt for strings.
unless
my $error = 0;
# unless = negated if:
unless ($error) {
say "All OK";
}
# Equivalent to:
if (!$error) {
say "All OK";
}
# Statement modifier:
say "No error" unless $error;unless runs the block when the condition is FALSE — more readable than if (!$cond) for absence checks. As a statement modifier at the end of the line, it reads naturally: "do this unless...".
do-while
my $input;
# Runs at least once:
do {
print "Command: ";
$input = <STDIN>;
chomp $input;
} while ($input ne "quit");
say "Goodbye!";The do { } while block runs the body before testing the condition, guaranteeing at least one execution. It is useful for menus and user input validation.
Spaceship and cmp
# spaceship (numeric): -1, 0 or 1
my $r = $a <=> $b;
# cmp (string): -1, 0 or 1
my $r = $a cmp $b;
# Used in sort:
my @asc = sort { $a <=> $b } @nums;
my @alpha = sort { $a cmp $b } @strings;The spaceship operator <=> compares numbers and cmp compares strings, returning -1, 0 or 1. They are the foundation of sorting blocks in sort.
Statement Modifiers
say "Positive" if $x > 0;
die "Error!" unless defined $val;
print "$_\n" for @list;
# Equivalent to blocks:
if ($x > 0) { say "Positive" }
for (@list) { print "$_\n" }
# while modifier:
$total += $_ while @nums;Statement modifiers (if, unless, for, while at the end of the line) are idiomatic for one-line actions. It reads the "action if condition", natural like English. Ideal for concise code.
next / last / redo
foreach my $n (1..20) {
next if $n % 2 == 0; # skips evens
last if $n > 15; # exits the loop
say $n; # only odds up to 15
}
# redo (restarts the iteration):
foreach my $item (@data) {
if (!valid($item)) {
$item = fix($item);
redo; # repeats with fixed $item
}
}next jumps to the next iteration, last exits the loop completely, and redo restarts the current iteration without advancing. They are the equivalent of continue, break and a "retry" in other languages.
Ternary Operator
my $age = 20;
# condition ? value_if_true : value_if_false
my $status = $age >= 18 ? "adult" : "minor";
say $status; # adult
# Chained (avoid!):
my $level = $grade >= 18 ? "A"
: $grade >= 10 ? "B"
: "C";
# Better with if/elsif for clarityThe ternary operator ?: chooses between two values in an expression. It is useful for simple assignments, but chaining several ternaries reduces readability — prefer if/elsif in those cases.
for / foreach
# foreach (idiomatic):
foreach my $item (@list) {
say $item;
}
# short for with $_ (alias):
say $_ * 2 for 1..10;
# C-style for:
for (my $i = 0; $i < 10; $i++) {
say $i;
}
# for and foreach are synonyms:
for my $x (@nums) { say $x }foreach iterates over lists without indices; the iteration variable is an alias of the original element. for and foreach are synonyms in Perl. $_ is the default variable when none is declared.
continue
while (<>) {
next if /^#/; # skips comments
process($_);
} continue {
$. = 0 if eof; # reset the line counter
}The continue block runs after each loop iteration, even when next is used. It works like the increment part of a C for — ideal for keeping counters or resetting state between lines.
Dispatch Table (switch)
# Hash of code refs (more Perl-ish than switch):
my %actions = (
new => sub { say "Create new" },
open => sub { say "Open file" },
save => sub { say "Save" },
);
my $cmd = "open";
if (exists $actions{$cmd}) {
$actions{$cmd}->(); # calls the sub
} else {
say "Unknown command";
}Dispatch tables (hash of code refs) are the idiomatic alternative to switch/case in Perl. They map values to actions in a flexible and extensible way. given/when exists but is experimental and discouraged.
References and Structures
Creating References
my @arr = (1, 2, 3);
my %hash = (a => 1);
sub func { 42 }
my $ref_arr = \@arr; # ref to array
my $ref_hash = \%hash; # ref to hash
my $ref_sub = \&func; # ref to sub
my $ref_scalar = \$arr[0]; # ref to scalar
# ref() checks the type:
ref($ref_arr); # "ARRAY"
ref($ref_hash); # "HASH"
ref($ref_sub); # "CODE"References are created with a backslash: \@arr, \%hash, \&sub. They are scalars that "point" to another variable. ref() returns the reference type (ARRAY, HASH, CODE).
Autovivification
my $data; # undef
# Perl creates the intermediate structures:
$data->{users}[0]{name} = "Anna";
# Now $data is:
# { users => [ { name => "Anna" } ] }
# Beware of typos (creates keys without error):
say $data->{usres}; # undef, no warning!Autovivification is the automatic creation of intermediate structures on access — convenient but it can mask bugs (typos create keys instead of raising errors). With use strict and care, it is a powerful feature.
Scalar::Util
use Scalar::Util qw(blessed reftype looks_like_number);
blessed($obj); # class or undef
reftype($ref); # ARRAY, HASH, CODE...
looks_like_number("42"); # True
looks_like_number("abc"); # False
# Check before dereferencing:
if (reftype($x) eq 'ARRAY') {
say "It is an array with " . scalar(@$x) . " elems";
}Scalar::Util provides type introspection: blessed returns the class of an object, reftype the reference type, looks_like_number validates numbers. Useful for defensive code before dereferencing.
Anonymous References
# Anonymous array ref:
my $arr = [1, 2, 3];
# Anonymous hash ref:
my $hash = { name => "Anna", age => 30 };
# Anonymous code ref:
my $sub = sub { 42 };
# Nested:
my $data = {
users => [ { name => "Anna" }, { name => "Ray" } ],
};[] creates an anonymous array reference and {} a hash one (without needing a named variable). They are the foundation of complex data structures in Perl — equivalent to literal arrays and objects in other languages.
Array of Hashes
# List of records (very common pattern):
my @staff = (
{ name => "Anna", dept => "IT", salary => 2500 },
{ name => "Ray", dept => "HR", salary => 2000 },
);
# Access:
say $staff[0]{name}; # Anna
$staff[1]{salary} += 200;
# Iterate:
for my $s (@staff) {
say "$s->{name}: $s->{dept}";
}An array of hashes is the pattern for lists of records (like SQL query results). Each element is a hash reference. Access combines index and key: $staff[0]{name}.
Trees (recursive structure)
# Recursive tree:
my $tree = {
value => "root",
children => [
{ value => "A", children => [] },
{ value => "B", children => [
{ value => "B1", children => [] },
]},
],
};
# Traverse (recursive):
sub print_tree {
my ($node, $level) = @_;
say " " x $level . $node->{value};
print_tree($_, $level + 1) for @{$node->{children}};
}
print_tree($tree, 0);Recursive structures like trees are built with nested hashes and arrays. Traversal is naturally recursive. " " x $level indents according to depth. Data::Dumper helps inspect these structures.
Dereferencing
my $arr = [1, 2, 3];
my $hash = { a => 1, b => 2 };
my $sub = sub { "hello" };
# Whole array:
my @copy = @$arr;
# Elements:
my $elem = $arr->[0]; # 1
my $val = $hash->{a}; # 1
my $r = $sub->(); # "hello"
# Without arrow (old syntax):
my $elem2 = $$arr[0];Dereferencing is done with @/%/$ before the reference, or with the arrow -> for elements: $arr->[0], $hash->{a}, $sub->(). The arrow is the most readable and recommended form.
Deep Copy
use Storable qw(dclone);
my $original = { a => [1, 2, 3], b => { x => 1 } };
# Shallow copy (shares refs):
my $shallow = $original;
# Deep copy (independent):
my $copy = dclone($original);
$copy->{a}[0] = 99;
say $original->{a}[0]; # 1 (unchanged)Assigning a reference copies the pointer, not the data (changes affect both). Storable::dclone makes a deep, independent copy — essential when you need to modify a structure without changing the original.
Chained Arrow
my $data = {
people => [
{ name => "Anna", city => "Lisbon" },
{ name => "Ray", city => "Porto" },
],
};
# Chained access:
my $city = $data->{people}[0]{city};
say $city; # Lisbon
# The arrow between ] and { is optional:
$data->{people}->[0]->{city}; # sameIn chained accesses like $data->{people}[0]{city}, the arrow between ] and { is optional — Perl infers the type automatically. This makes access to complex structures concise and readable.
Weak References
use Scalar::Util qw(weaken);
my $a = { name => "parent" };
my $b = { name => "child" };
$a->{child} = $b;
$b->{parent} = $a; # reference cycle!
weaken($b->{parent}); # does not count for GC
# Now $b->{parent} does not stop $a from being freedWeak references (weaken) do not count for the garbage collector — essential in circular structures. Without weaken, reference cycles are never freed (memory leak), since Perl reference counting cannot detect them.
Regular Expressions
Basic Match
my $text = "Hello World 123";
# =~ operator (match):
if ($text =~ /World/) {
say "Found it";
}
# Negation !~:
if ($text !~ /xyz/) {
say "Not found";
}
# In implicit boolean context ($_):
if (/pattern/) { say "in $_" }The =~ operator applies a regex pattern to a string and returns true if it matches. !~ negates. Without =~, the match operates on $_ (the default variable). It is the foundation of all text processing in Perl.
Capturing with Parentheses
my $date = "2024-01-15";
if ($date =~ /(\d{4})-(\d{2})-(\d{2})/) {
my ($year, $month, $day) = ($1, $2, $3);
say "Year: $year, Month: $month, Day: $day";
}
# In list context:
my ($h, $m) = "14:30" =~ /(\d+):(\d+)/;Parentheses capture groups into the variables $1, $2, $3... In list context, the match returns all captures at once. It is the main way to extract parts of a string.
Transliteration tr///
my $text = "Hello World"; # Uppercase (range): $text =~ tr/a-z/A-Z/; # "HELLO WORLD" # Remove vowels (/d delete): $text =~ tr/aeiou//d; # Count occurrences: my $n = ($text =~ tr/a//); # number of 'a' # tr/// is NOT regex: # it operates character by character
tr/// transliterates character by character (it is not regex). tr/a-z/A-Z/ converts to uppercase, /d removes the listed characters. In scalar context it returns the number of replaced characters — useful for counting.
Metacharacters
/^start/ # start of line
/end$/ # end of line
/a.c/ # any char between a and c
/colou?r/ # optional u (color/colour)
/ab+c/ # b one or more times
/ab*c/ # b zero or more times
/a{2,4}/ # a between 2 and 4 times
/\d+/ # one or more digitsMetacharacters define patterns: ^ start, $ end, . any character, ? optional, + one or more, * zero or more, {n,m} quantity. \d is a digit. They cover 90% of common patterns.
Named Captures
my $date = "2024-01-15";
if ($date =~ /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/) {
say "$+{year}/$+{month}/$+{day}";
}
# More readable than $1, $2, $3 in complex regexes
# %+ holds the named capturesNamed captures (?<name>...) store the result in %+ (accessed via $+{name}). They are much more readable than $1, $2 in complex regexes, since the name documents the meaning of each group.
Lookahead and Lookbehind
# Positive lookahead (?=): /\d+(?=€)/ # number followed by € # Negative lookahead (?!): /\d+(?!€)/ # number NOT followed by € # Positive lookbehind (?<=): /(?<=€)\d+/ # number preceded by € # Negative lookbehind (?<!): /(?<!€)\d+/ # number NOT preceded by € # They do not consume characters
Lookahead (?=) and lookbehind (?<=) check the context without consuming characters — they validate format without capturing the delimiter. The negative versions (?! and (?<! check for absence. Essential for precise validation.
Character Classes
/\d/ # digit [0-9] /\w/ # word char [a-zA-Z0-9_] /\s/ # whitespace (space, tab, \n) /\D/ # non-digit /\W/ # non-word char /\S/ # non-whitespace /[aeiou]/ # one vowel /[^0-9]/ # any non-digit /[a-z]+/ # lowercase letters # Dot does not match \n by default: /./s # with /s, matches \n too
Classes [] define sets of accepted characters; [^...] negates. Shortcuts: \d digit, \w word char, \s whitespace (and uppercase versions negate). The . matches any character except newline (unless you use /s).
Substitution s///
my $text = "Hello World"; # Simple substitution: $text =~ s/World/Perl/; # "Hello Perl" # Global (all occurrences): $text =~ s/o/0/g; # With backreferences: my $email = "ana@mail.com"; $email =~ s/(\w+)@(.+)/user: $1, dom: $2/; # Modifiers: s///gi, s///e
s/// replaces text: s/old/new/. The /g modifier replaces all occurrences. Backreferences $1, $2 reuse captured groups in the replacement. It is the central text transformation tool.
qr// and Reusable Patterns
# qr// compiles a regex into an object:
my $date_re = qr/\d{4}-\d{2}-\d{2}/;
if ($text =~ $date_re) {
say "Has a date";
}
# Pattern composition:
my $num = qr/\d+/;
my $sep = qr/[\/-]/;
my $date2 = qr/$num$sep$num$sep$num/;
# Interpolates into other regexesqr// compiles a regex into a reusable, interpolatable object. It allows building complex patterns by composition (qr/$num$sep$num/) and sharing regexes across several operations. It accepts the same modifiers.
Modifiers
/pattern/i # case-insensitive
/pattern/g # global (all occurrences)
/pattern/m # multiline (^ $ per line)
/pattern/s # . matches \n too
/pattern/x # allows spaces and comments
# /x example (readable regex):
my $re = qr/
^ \d{4} # year
- \d{2} # month
- \d{2} # day
$/x;Modifiers change the behavior: /i ignores case, /g finds all occurrences, /m makes ^/$ act per line, /x allows spaces and comments to make regexes readable. They combine: /gix.
s///e (code in the replacement)
# /e evaluates the replacement the Perl code: my $text = "3 5 8"; $text =~ s/(\d+)/$1 * 2/ge; # "6 10 16" # Uppercase each word: my $t = "hello world"; $t =~ s/(\w+)/ucfirst($1)/ge; # "Hello World" # Without /e, $1*2 would be literal
The /e modifier evaluates the replacement the Perl code instead of a string — $1 * 2 computes the double instead of staying literal. Combined with /g, it applies the transformation to each match. Ideal for dynamic transformations.
split with Regex and \K
# split with a flexible pattern:
my @fields = split(/\s*,\s*/, "a, b ,c");
# ("a", "b", "c")
# \K (resets the match start):
my $s = "foobar";
$s =~ s/foo\Kbar/baz/; # "foobaz"
# replaces "bar" but keeps "foo"
# Equivalent to: s/(foo)bar/${1}baz/
# but \K is simplersplit accepts regex for flexible separators (\s*,\s* ignores spaces). \K "forgets" what matched before — in s/foo\Kbar/baz/ it replaces only bar keeping foo, simplifying the use of backreferences.
OO e Modules
Package and Module
# File: Animal.pm
package Animal;
use strict;
use warnings;
sub new {
my ($class, %args) = @_;
return bless {
name => $args{name},
sound => $args{sound} // "...",
}, $class;
}
sub speak {
my $self = shift;
return "$self->{name}: $self->{sound}!";
}
1; # modules MUST end with trueA module is a .pm file with a package matching the path. bless associates a hash reference with a class, creating an object. The new constructor is a convention. The 1; at the end is mandatory — use checks that the module returned true.
Moose (full OO)
package Account;
use Moose;
has holder => (
is => 'ro',
isa => 'Str', # type constraint
required => 1,
);
has balance => (
is => 'rw',
isa => 'Num',
default => 0,
);
sub withdraw {
my ($self, $amount) = @_;
die "Insufficient balance" if $amount > $self->balance;
$self->balance($self->balance - $amount);
}
__PACKAGE__->meta->make_immutable;Moose is the most complete OO system: isa => 'Num' validates types automatically, make_immutable optimizes the class. The penalty is load time — that is why Moo exists the a lightweight alternative. It inspired modern OO in Perl.
AUTOLOAD
package Config;
our $AUTOLOAD;
sub AUTOLOAD {
my $self = shift;
(my $method = $AUTOLOAD) =~ s/.*:://;
if (@_) {
$self->{$method} = shift; # setter
} else {
return $self->{$method}; # getter
}
}
my $cfg = bless {}, 'Config';
$cfg->name("Anna"); # created dynamically
say $cfg->name; # "Anna"AUTOLOAD is called when a method does not exist — it allows creating dynamic getters/setters. The $AUTOLOAD variable contains the name of the called method. It is powerful but makes debugging harder; Moo/Moose generate accessors statically.
Creating and Using Objects
use Animal;
# Create an object:
my $a = Animal->new(name => "Rex", sound => "Woof");
# Call methods (arrow ->):
say $a->speak; # "Rex: Woof!"
# $self is the object (1st argument):
sub speak {
my $self = shift; # takes the object
return $self->{name};
}Objects are created with Class->new(...) and methods are called with the arrow ->. The first argument of every method is the object itself ($self), taken with shift. The data lives in the hash $self->{field}.
Roles (composition)
# Role (reusable behavior):
package Serializable;
use Moo::Role;
requires 'to_hash'; # required method
sub to_json {
my $self = shift;
return encode_json($self->to_hash);
}
# Consumption:
package User;
use Moo;
with 'Serializable';
sub to_hash { { name => $_[0]->name } }Roles are behavior composition — the modern alternative to multiple inheritance. requires declares methods the consumer must implement; with applies the role. They avoid the "diamond problem" of multiple inheritance.
DESTROY
package Connection;
sub new { bless { dbh => connect_db() }, shift }
# Destructor (called at garbage collection):
sub DESTROY {
my $self = shift;
$self->{dbh}->disconnect if $self->{dbh};
say "Connection closed";
}
# If you use AUTOLOAD, define an empty DESTROY:
sub DESTROY { } # prevents AUTOLOADDESTROY is the destructor — called automatically when the object is garbage collected. Essential to close connections and free resources. If you use AUTOLOAD, always define a DESTROY (even empty) to intercept it.
Inheritance
package Dog;
use parent 'Animal'; # inherits from Animal
sub speak { "Woof woof" } # override
package GuideDog;
use parent 'Dog';
sub speak {
my $self = shift;
my $base = $self->SUPER::speak(); # calls the parent
return "$base (guide dog)";
}
my $d = Dog->new(name => "Rex");
say $d->isa('Animal'); # 1use parent sets up inheritance (the @ISA list). SUPER:: calls as parent class method. isa() checks whether an object is of a class (or derived). Perl supports multiple inheritance: use parent 'A', 'B'.
Encapsulation
package Bank;
use Moo;
# private setter (rwp):
has balance => (
is => 'rwp', # read-write private
init_arg => undef, # not in the constructor
default => 0,
);
sub deposit {
my ($self, $amount) = @_;
$self->_set_balance($self->balance + $amount);
}
# _set_balance is only accessible inside the classPerl has no native private, but Moo/Moose offer is => 'rwp' (private setter _set_balance) and init_arg => undef (excludes from the constructor). The _prefix convention means "do not touch" the internal data.
Signatures (Perl 5.36+)
use v5.36;
# Signatures do automatic destructuring:
sub add ($a, $b) {
return $a + $b;
}
# With defaults and optionals:
sub greet ($name, $prefix = "Hello") {
return "$prefix, $name!";
}
# Checks arity automatically:
add(1, 2); # OK
add(1); # ERROR (missing args)Signatures (Perl 5.36+) are the modern way to declare parameters: sub add ($a, $b) destructures @_ automatically and checks arity. They replace old prototypes and the manual my ($a, $b) = @_.
Moo (lightweight modern OO)
package Person;
use Moo;
has name => (
is => 'ro', # read-only
required => 1,
);
has age => (
is => 'rw', # read-write
default => sub { 0 },
);
# Usage:
my $p = Person->new(name => "Anna", age => 30);
say $p->name; # Anna (getter)
$p->age(31); # setter (rw)Moo is minimalist modern OO. has declares attributes with automatic accessors: is => 'ro' creates only a getter, 'rw' creates getter and setter. required => 1 makes it mandatory; default sets the default value. It is compatible with Moose.
Operator Overloading
package Vector;
use overload
'+' => sub {
my ($self, $other) = @_;
Vector->new($self->{x} + $other->{x},
$self->{y} + $other->{y});
},
'""' => sub { "($_[0]{x}, $_[0]{y})" },
fallback => 1;
my $a = Vector->new(1, 2);
my $b = Vector->new(3, 4);
my $c = $a + $b; # Vector(4, 6)
say $c; # "(4, 6)"use overload makes objects behave like native values: + adds, "" converts to string (used in say). fallback => 1 allows deriving undefined operators. Essential for mathematical classes.
Class vs Object Methods
package Counter;
my $total = 0; # class variable
sub new { bless {}, shift }
# Class method (called on the class):
sub grand_total {
return $total;
}
# Object method (called on the instance):
sub increment {
my $self = shift;
$total++;
}
Counter->increment(); # $self = "Counter"
my $c = Counter->new();
$c->increment(); # $self = object
say Counter->grand_total(); # 2In Perl, class and object methods use the same -> syntax. The difference is in as first argument: on the class, the class name arrives; on the instance, the object arrives. shift takes it in both cases.
Files and I/O
open for Reading
# Three-argument form (safe):
open(my $fh, "<", "file.txt")
or die "Cannot open: $!";
# Read everything:
my @lines = <$fh>;
# Or line by line:
while (my $line = <$fh>) {
chomp $line;
say $line;
}
close $fh;open with three arguments (open $fh, MODE, FILE) is the safe pattern — it avoids filename injection. < is reading. or die $! exits with the system error message on failure. Always close with close.
Diamond Operator
# Reads from the files in @ARGV or STDIN:
while (my $line = <>) {
chomp $line;
print "$.: $line\n"; # $. = line number
}
# Usage: perl script.pl f1.txt f2.txt
# or: cat f.txt | perl script.pl
# In-place editing:
# perl -pi -e 's/foo/bar/g' *.txtThe diamond operator <> reads lines from the files passed in @ARGV or from STDIN — the foundation of Unix text filters. $. is the current line number. With -pi -e it edits files in place.
Directories
use File::Path qw(make_path remove_tree);
use File::Copy qw(copy move);
# Create folder (recursive):
make_path("data/logs/2024");
# List contents:
opendir(my $dh, "data") or die $!;
my @files = grep { !/^\./ } readdir($dh);
closedir $dh;
# Copy / move:
copy("a.txt", "b.txt") or die $!;
move("b.txt", "archive/b.txt");File::Path creates and removes folders recursively (make_path). opendir/readdir list contents (filter . and .. with grep). File::Copy copies and moves files. They are Perl core modules.
open for Writing and Append
# Writing (truncates the file):
open(my $out, ">", "output.txt")
or die "Cannot create: $!";
print $out "Line $_\n" for 1..10;
close $out;
# Append (adds at the end):
open(my $log, ">>", "app.log")
or die "Error: $!";
say $log "[" . localtime . "] event";
close $log;> opens for writing (erases existing content); >> opens for append (adds at the end). print $out and say $log write to the filehandle. Always use or die to handle open failures.
Special I/O Variables
$! # system error (errno)
$. # line number of the last handle
$_ # default variable
$/ # input separator (default \n)
$\ # output separator
$| # autoflush (1 = no buffer)
# Read paragraphs:
local $/ = ""; # paragraph mode
while (my $to = <$fh>) { ... }
# Full slurp:
local $/ = undef;
my $all = <$fh>;Perl has special I/O variables: $! is the system error, $. the line number, $/ the input separator. Changing $/ to "" reads by paragraphs; to undef it reads the whole file (slurp).
File::Find (recursive)
use File::Find;
# Walk the directory tree:
find(\&process, "data");
sub process {
return unless -f $_; # files only
return unless /\.pm$/; # only .pm
say "$File::Find::name"; # full path
}
# Find large files:
find(sub {
say "$File::Find::name" if -f && -s > 1_000_000;
}, ".");File::Find recursively walks a directory tree, calling a sub for each entry. $_ is the name and $File::Find::name the full path. The -f (file) and -s (size) operators filter the results.
UTF-8 Encoding
# Reading with encoding:
open(my $fh, "<:encoding(UTF-8)", "f.txt")
or die "Error: $!";
# Writing with encoding:
open(my $out, ">:encoding(UTF-8)", "o.txt")
or die "Error: $!";
# Pragmas for the whole script:
use open ':std', ':encoding(UTF-8)';
use utf8; # source code in UTF-8The :encoding(UTF-8) layer guarantees correct Unicode reading/writing. Without it, accented characters can get corrupted. use utf8 declares that as source code is UTF-8; use open applies encoding to all handles.
STDIN, STDOUT, STDERR
# Read from the keyboard: print "Name: "; my $name = <STDIN>; chomp $name; # Write to the error stream: say STDERR "Warning!"; # Redirect STDOUT: open(my $saved, ">&", \*STDOUT); open(STDOUT, ">", "log.txt"); say "goes to the file"; open(STDOUT, ">&", $saved); # restore
STDIN, STDOUT and STDERR are the standard filehandles (input, output, error). say STDERR writes to the error stream without affecting normal output. They can be redirected to files with open.
Path::Tiny
use Path::Tiny;
# Read the whole file:
my $content = path("f.txt")->slurp_utf8;
# Write:
path("out.txt")->spew_utf8($content);
# Lines:
my @lines = path("f.txt")->lines_utf8;
# Other operations:
path("data")->mkpath; # create folder
path("f.txt")->exists; # exists?
path("f.txt")->basename; # "f.txt"Path::Tiny (CPAN) modernizes file I/O: slurp_utf8 reads everything, spew_utf8 writes, with automatic encoding. It is much more concise than manual open/close and handles errors predictably.
die and errno
# Idiomatic error pattern:
open(my $fh, "<", $file)
or die "Cannot open '$file': $!";
# $! contains the system message:
# "No such file or directory"
# die with exit code:
die "Fatal error\n"; # exit code != 0
# warn (does not terminate):
warn "Warning: $!" if $problem;or die is the error handling pattern in Perl: try the operation or exit with a message. $! contains the system error description. die terminates the program; warn only prints a warning and continues.
Tips and Good Practices
strict and warnings always
# Always at the top of every script/module: use strict; use warnings; # Or modern (enables both): use v5.36; # strict: typos become compile errors # warnings: suspicious operations produce warnings # Without them, silent bugs: $nam = "Anna"; # typo! global variable
use strict and use warnings are mandatory in modern Perl. strict turns variable typos into compile errors; warnings alerts about suspicious operations. use v5.36 enables both automatically.
Naming Conventions
$scalar # scalar variable @array # array %hash # hash &sub # subroutine (rare) # Names: my $total_count; # snake_case sub calculate_discount; # snake_case package My::Class; # PascalCase # Constants: use constant MAX => 100; # or Readonly my $MAX => 100;
Perl conventions: variables and subs in snake_case, packages in PascalCase (with :: the separator). The sigil ($, @, %) indicates the type. use constant creates constants. Prefer descriptive names.
// vs ||
my $port = 0; # || treats 0 and "" the false (DANGER): my $a = $port || 3306; # 3306 (wrong!) # // only uses the default if undef (SAFE): my $b = $port // 3306; # 0 (correct!) # Rule: use // for values that can # legitimately be 0 or empty string
The || vs // distinction is critical: || treats 0, "" and "0" the false (returning the default wrongly), while // only triggers on undef. Use // for values that can legitimately be zero or empty string.
Essential Modules
# Always useful (core): use List::Util qw(sum max first); use Scalar::Util qw(blessed reftype); use File::Path qw(make_path); use File::Basename qw(basename); use Getopt::Long; use POSIX qw(strftime); # Recommended CPAN: use Path::Tiny; # modern I/O use Try::Tiny; # exceptions use JSON; # JSON use DateTime; # dates
These modules solve 90% of common tasks: List::Util and Scalar::Util (core) for collections and types; Path::Tiny, Try::Tiny, JSON and DateTime (CPAN) modernize I/O, exceptions, serialization and dates.
$_ the Default Variable
# $_ is used when nothing is specified:
for (@list) { say } # say $_
print if /pattern/; # $_ =~ /pattern/
my $x = length; # length($_)
# map/grep use $_:
my @d = map { $_ * 2 } @n;
# Careful: $_ is global and shared
# In complex code, use named variables$_ is the Perl default variable — used by print, say, map, grep, regexes and many functions when no argument is given. It makes code concise, but in excess it reduces clarity. Use in moderation.
Debugging
# Inspect variables: use Data::Dumper; warn Dumper($structure); # warn does not terminate (good for debug): warn "Here: $value\n"; # Interactive debugger: # perl -d script.pl # Debugger commands: # s (step), n (next), p $var (print) # b 10 (breakpoint line 10), c (continue)
Data::Dumper + warn is the quick way to inspect variables without stopping the program. For serious debugging, perl -d opens an interactive debugger with breakpoints (b), step (s) and inspection (p).
One-liners
# Replace in files (-pi -e):
perl -pi -e 's/foo/bar/g' *.txt
# Print matching lines:
perl -ne 'print if /error/' log.txt
# Sum the 2nd column:
perl -lane '$s += $F[1]; END { say $s }' data
# -n: while(<>){...} -l: chomp + \n
# -a: split into @F -e: codePerl one-liners are powerful for text processing: -n wraps in while(<>), -p adds print, -l does chomp and newline, -a splits into @F, -i edits in-place. They replace awk/sed in many tasks.
General Best Practices
# 1. strict + warnings always
use v5.36;
# 2. // instead of || for defaults
my $x = $input // "default";
# 3. Three-arg open + or die
open(my $fh, "<", $f) or die $!;
# 4. Descriptive names, not too much $_
for my $client (@clients) { ... }
# 5. Modules instead of reinventing
use List::Util qw(sum);Perl best practices: enable strict/warnings, use // for defaults, open with three arguments and or die, descriptive names instead of excessive $_, and CPAN modules instead of reinventing the wheel. Perl code should be readable, not "magic".