Getting Started
Basics & Hello World
Perl uses sigils to denote variable types: $ for scalar, @ for array, % for hash. Always use strict and warnings for cleaner code.
#!/usr/bin/perl
use strict;
use warnings;
print "Hello, World!\n";
# variables: scalar ($), array (@), hash (%)
my $name = "Alice";
my @nums = (1, 2, 3);
my %ages = (Alice => 30, Bob => 25);
print "Name: $name\n";Comments
Single-line comments start with #. Multi-line comments use POD blocks delimited by =pod and =cut. POD is also used for documentation.
#!/usr/bin/perl
# This is a single-line comment
print "Hello\n"; # inline comment
=pod
This is a multi-line
comment using POD (Plain Old Documentation)
=cut
print "After POD\n";Scalars Introduction
Scalars hold a single value: strings, numbers, or references. Perl auto-converts between strings and numbers as needed. Use . to concatenate and x to repeat strings.
my $str = "Hello";
my $num = 42;
my $float = 3.14;
my $bool = 1; # true (0, "", "0", undef are false)
# String repetition
my $line = "-" x 20;
# String concatenation
my $greeting = "Hello, " . $str;
print "$greeting\n"; # Hello, HelloString Interpolation
Double-quoted strings interpolate $ and @ variables; single quotes are literal. Use braces around a variable name when followed by characters that would extend the identifier, e.g. to pluralize.
my $name = "Alice";
my $age = 30;
print "Name: $name\n"; # interpolates
print 'Name: $name\n'; # literal (no interpolation)
# Disambiguate variable name with braces
my $fruit = "apple";
print "I have many ${fruit}s.\n"; # I have many apples.
# Array interpolation (joins with $")
my @arr = (1, 2, 3);
print "Array: @arr\n"; # Array: 1 2 3use strict & warnings
strict forces lexical scoping (my/our) and catches typos. warnings enables useful diagnostics. For modern code, 'use v5.36' (or higher) bundles these with new features.
use strict;
use warnings;
# Without strict, this would create a global:
# $foo = 42; # ERROR under strict
my $foo = 42; # OK with my
# Warnings catch common mistakes
my $undef_var;
# print $undef_var; # warns: uninitialized value
# Modern bundle (v5.36+ enables strict, warnings, features)
use v5.36;
# Disable specific warnings locally
no warnings 'uninitialized';say & features
say is like print but appends a newline. Enable features with 'use feature' or a version bundle like 'use v5.36'. Signatures give named parameters instead of unpacking @_.
use v5.10;
use feature 'say';
say "Hello!"; # adds newline automatically
say "No need for \n";
# Equivalent to:
print "Hello!\n";
# Signatures (v5.20+, not default)
use feature 'signatures';
no warnings 'experimental::signatures';
sub greet ($name) {
say "Hi, $name";
}Scalars
Numbers & Operators
Perl has full arithmetic operators. Division always yields a float unless you use int(). The ** operator is exponentiation. ++ and -- work on numbers and strings (auto-incrementing 'a' gives 'b').
my $sum = 5 + 3; # 8
my $diff = 10 - 4; # 6
my $prod = 6 * 7; # 42
my $div = 10 / 3; # 3.3333...
my $int = int(10 / 3); # 3
my $mod = 10 % 3; # 1
my $pow = 2 ** 10; # 1024
# Augmented assignment
my $x = 5;
$x += 3; # $x = 8
$x **= 2; # $x = 64
$x++; # $x = 65
$x--; # $x = 64String Operators
Use . for concatenation (not +) and x for repetition. String comparison uses eq/ne/lt/gt; numeric uses ==/!=/</>. Mixing them can produce surprising results.
my $a = "Hello";
my $b = "World";
# Concatenation
my $combined = $a . ", " . $b; # Hello, World
# Repetition
my $line = "=" x 30;
# Augmented concatenation
my $str = "abc";
$str .= "def"; # abcdef
# Numeric vs string comparison
print "match" if "abc" eq "abc"; # string equal
print "match" if 123 == 123; # numeric equalundef & defined
undef represents an undefined (or absent) value. Use defined() to test for it. The // (defined-or) operator returns the right side only if the left is undef, unlike || which also triggers on 0 or ''.
my $x; # undef
print "undef\n" if !defined $x;
$x = undef; # explicitly undef
undef $x; # another way
# Defined-or operator
my $val = $x // "default";
# Warning if used as string:
# print $x; # Use of uninitialized value
if (defined $val) {
print "Defined: $val\n";
}Quoting (q/qq/qw)
q// is single-quote, qq// is double-quote, qw// splits on whitespace into a list. You can use any matching delimiters ((), {}, [], //, !!) which helps avoid escaping quotes inside strings.
my $name = "Alice";
# q() = single quote (no interpolation)
my $s1 = q(Hello $name); # Hello $name
# qq() = double quote (interpolation)
my $s2 = qq(Hello $name); # Hello Alice
# qw() = quoted words (produces a list)
my @words = qw(apple banana cherry);
# equivalent to ("apple", "banana", "cherry")
# Custom delimiters
my $s3 = q{don't worry};
my $s4 = q!bang!;Here-doc
Heredocs let you write multi-line strings. <<'END' is literal (no interpolation), <<"END" interpolates. The ~ variant (v5.26+) strips leading whitespace so the heredoc can be indented with surrounding code.
my $name = "Alice";
# Single-quote style (no interpolation)
my $text = <<'END';
Hello $name,
This is literal.
END
# Double-quote style (interpolation)
my $msg = <<"EOF";
Hello $name,
Interpolated here.
EOF
print $msg;
# Indented heredoc (v5.26+)
my $code = <<~EOT;
line 1
line 2
EOTBoolean Truthiness
Only undef, 0, '', and '0' are false; even '00' and '0.0' are true! Use || for truthy defaults and // for defined defaults. References (even empty arrays) are always true.
# False values: undef, 0, "", "0"
# Everything else is true
print "true\n" if 1; # true
print "true\n" if "hello"; # true
print "true\n" if "0"; # NOT printed (false!)
print "true\n" if ""; # NOT printed (false)
print "true\n" if []; # true (reference is true)
# Logical operators
my $x = 0 || "default"; # default (|| tests truthiness)
my $y = 0 // "default"; # default (// tests definedness)
my $z = "a" && "b"; # b (returns last evaluated)Arrays
Array Basics
Arrays use @ sigil for the whole array and $ for individual elements (because an element is a scalar). $#arr gives the last index. In scalar context an array returns its length.
my @nums = (1, 2, 3, 4, 5);
my @strs = qw(a b c);
my @mixed = (1, "two", 3.14);
# Access by index (0-based)
print $nums[0]; # 1
print $nums[-1]; # 5 (last element)
# Modify an element
$nums[0] = 100;
# Range operator
my @range = (1 .. 10); # 1 to 10
# Length in scalar context
my $count = scalar @nums; # 5
my $count2 = @nums; # 5push/pop/unshift/shift
push/pop add/remove at the end (fast); unshift/shift add/remove at the start (slower, reindexes). splice is the general tool to add/remove/replace anywhere.
my @stack = (1, 2, 3);
# push/pop operate on the END
push @stack, 4, 5; # (1, 2, 3, 4, 5)
my $last = pop @stack; # 5
# unshift/shift operate on the START
unshift @stack, 0; # (0, 1, 2, 3, 4)
my $first = shift @stack; # 0
# @stack is now (1, 2, 3, 4)
print "@stack\n";
# splice is the general-purpose tool
splice(@stack, 1, 0, 'x'); # insert at index 1Array Slicing
An array slice @arr[indices] returns a list of elements. Note the @ sigil (you get a list, not a scalar). Use ranges 0..2 for contiguous slices and $#arr for the last index.
my @nums = (10, 20, 30, 40, 50);
# Array slice (returns a list)
my @slice = @nums[1, 3]; # (20, 40)
my @range = @nums[0 .. 2]; # (10, 20, 30)
# Modify multiple elements via slice
@nums[1, 3] = (200, 400);
# Last index
my $last_idx = $#nums; # 4
# Negative indices count from the end
print $nums[-1]; # 50
print $nums[-2]; # 400splice
splice is the universal array mutation tool: remove, insert, or replace any contiguous chunk. LENGTH 0 inserts without removing; omitting LENGTH removes to the end.
my @arr = (1, 2, 3, 4, 5);
# splice ARRAY, OFFSET, LENGTH, LIST
my @removed = splice(@arr, 1, 2); # removes (2, 3)
# @arr is now (1, 4, 5)
# Insert without removing (LENGTH = 0)
splice(@arr, 1, 0, 'a', 'b'); # (1, 'a', 'b', 4, 5)
# Replace elements
splice(@arr, 0, 2, 'x'); # replace first 2 with 'x'
# Remove everything from offset to end
my @rest = splice(@arr, 2);Sorting
sort takes a comparison block using special variables $a and $b. Use <=> for numeric comparison and cmp for string comparison. The Schwartzian transform caches sort keys for efficiency.
my @nums = (3, 1, 4, 1, 5, 9);
# Default sort is lexicographic (string)
my @s1 = sort @nums;
# Numeric ascending
my @s2 = sort { $a <=> $b } @nums;
# Numeric descending
my @desc = sort { $b <=> $a } @nums;
# String sort
my @words = qw(banana apple Cherry);
my @alpha = sort { lc($a) cmp lc($b) } @words;
# Schwartzian transform (sort by computed key)
my @sorted = map { $_->[0] }
sort { $a->[1] <=> $b->[1] }
map { [$_, length($_)] } @words;Array length & scalar context
An array's behavior depends on context: scalar context gives length, list/string context gives elements joined by $" (default space). Use scalar() to force scalar context when needed.
my @arr = (1, 2, 3, 4, 5);
# Scalar context returns length
my $len = @arr; # 5
print "Length: $len\n";
# String context joins with $"
print "Array: @arr\n"; # 1 2 3 4 5
# Change separator
local $" = ", ";
print "@arr\n"; # 1, 2, 3, 4, 5
# Last index
print "Last index: $#arr\n"; # 4
# Force scalar context
print scalar(@arr); # 5Hashes
Hash Basics
Hashes (associative arrays) use % sigil. The fat comma => quotes its left side and is preferred over a plain comma. Access individual values with $hash{key} (scalar context, single value).
my %ages = (Alice => 30, Bob => 25, Carol => 35);
# Fat comma => auto-quotes the left side
my %h2 = (name => 'Alice', age => 30);
# Same as list of pairs:
my %h3 = ('name', 'Alice', 'age', 30);
# Access / modify a value
print $ages{Alice}; # 30
$ages{Dave} = 40; # add
$ages{Alice} = 31; # modify
# Delete a key
delete $ages{Bob};Iteration (keys/values/each)
keys/values return lists (unordered). each iterates one pair at a time and is memory-efficient for large hashes but don't modify the hash during iteration. Sort keys for deterministic order.
my %h = (a => 1, b => 2, c => 3);
# Iterate keys
for my $k (keys %h) {
print "$k => $h{$k}\n";
}
# Get all values
my @vals = values %h;
# each() returns key/value pairs iteratively
while (my ($k, $v) = each %h) {
print "$k = $v\n";
}
# Sorted keys
for my $k (sort keys %h) { ... }exists & delete
exists tests whether a key is in the hash (even if its value is undef). defined tests the value itself. delete removes a key and returns its value. A hash with no keys is false in boolean context.
my %h = (name => 'Alice', age => 30);
# exists checks if a key is present
if (exists $h{name}) {
print "Name exists\n";
}
# defined checks if the value is defined
if (defined $h{age}) { ... }
# delete removes a key, returns its value
my $removed = delete $h{age};
# Delete multiple keys (slice)
delete @h{qw(name age)};
# Empty a hash
%h = ();Hash Slices
@h{keys} returns the values for those keys (note the @ sigil). Since v5.20, %h{keys} returns key/value pairs. Slices are handy for fetching or setting multiple values at once.
my %h = (a => 1, b => 2, c => 3);
# Hash slice (returns list of values)
my @vals = @h{qw(a b)}; # (1, 2)
# Modify slice
@h{qw(a b)} = (10, 20);
# Key/value slice (v5.20+)
my @pairs = %h{qw(a b)}; # (a, 10, b, 20)
# Index/value slice
my @iv = %h{qw(a b)};
# Check multiple keys
my @present = grep { exists $h{$_} } qw(a x b);Multidimensional Hashes
Perl hashes hold scalar values, so nested structures use hash/array references. Auto-vivification automatically creates intermediate references when you assign to a deep key, which is convenient but can hide bugs.
my %people = (
alice => { age => 30, city => 'NYC' },
bob => { age => 25, city => 'LA' },
);
# Access nested values
print $people{alice}{age}; # 30
print $people{bob}{city}; # LA
# Add new nested entry
$people{carol}{age} = 35;
$people{carol}{city} = 'SF';
# Auto-vivification creates intermediate refs
$people{dave}{hobbies}[0] = 'reading';
# %people{dave} created automaticallyCommon Hash Operations
scalar keys %h gives the count. reverse %h swaps keys/values (lossy if values aren't unique). Merging with (%a, %b) flattens then rebuilds. List::Util provides min/max over the values.
my %h = (a => 1, b => 2, c => 3);
# Number of keys
my $count = scalar keys %h;
# Reverse a hash (values become keys)
my %rev = reverse %h; # (1 => a, 2 => b, 3 => c)
# Merge hashes
my %merged = (%h, d => 4);
# Check if empty
print "empty\n" if !keys %h;
# Reset each() iterator
keys %h; # resets iterator (v5.18+)
# Get min/max value
use List::Util qw(min max);
my ($min, $max) = (min(values %h), max(values %h));Control Structures
if/elsif/else
Perl uses elsif (no second 'e'). The statement modifier form (statement if EXPR) reads naturally for simple cases. All blocks require braces, even for one statement.
my $score = 85;
if ($score >= 90) {
print "A\n";
} elsif ($score >= 80) {
print "B\n";
} elsif ($score >= 70) {
print "C\n";
} else {
print "F\n";
}
# Statement modifier form
print "Pass\n" if $score >= 60;
# Note: it is 'elsif', NOT 'elseif'unless
unless is the negation of if. It reads well as a statement modifier (do X unless Y) but unless/else can be confusing. Prefer if with ! for complex conditions.
my $logged_in = 0;
unless ($logged_in) {
print "Please log in\n";
}
# Equivalent: if (!$logged_in) { ... }
# Statement modifier
print "Error\n" unless $ok;
# unless/else (often avoid for readability)
unless ($ok) {
print "fail\n";
} else {
print "pass\n";
}while & until
while repeats while the condition is true; until repeats until it's true. The do { } while/until form guarantees at least one execution. Use last to break out of an infinite loop.
# while loop
my $i = 0;
while ($i < 5) {
print "$i\n";
$i++;
}
# until = while NOT
my $done = 0;
until ($done) {
$done = check_something();
}
# do-while style (runs at least once)
my $n = 0;
do {
print $n++;
} while ($n < 3);
# Infinite loop
while (1) { last if $done; }for & foreach
for and foreach are aliases in Perl. The C-style form has three parts; the list form iterates a list. Without an explicit loop variable, the default $_ is used.
# C-style for loop
for (my $i = 0; $i < 5; $i++) {
print "$i\n";
}
# foreach over a list (for and foreach are interchangeable)
foreach my $item (1, 2, 3) {
print "$item\n";
}
# Range operator
for my $n (1 .. 10) {
print "$n ";
}
# Uses default $_ variable
for (1 .. 5) {
print "$_\n";
}last/next/redo
last exits the loop (like break), next skips to the next iteration (like continue), redo restarts the current iteration without re-evaluating the condition. Labels let you control outer loops.
# last = break out of loop
for my $i (1 .. 10) {
last if $i == 5;
print "$i ";
}
# prints: 1 2 3 4
# next = skip to next iteration
for my $i (1 .. 5) {
next if $i % 2 == 0;
print "$i "; # 1 3 5
}
# redo = re-run current iteration without re-checking
my $count = 0;
while (<$fh>) {
$count++;
redo if $count < 3;
}
# Labeled loops for nested control
OUTER: for my $i (1..3) {
for my $j (1..3) {
last OUTER if $i == 2 && $j == 2;
}
}given/when (switch)
given/when provides switch/case semantics using smart matching. It's experimental, so disable the warning or prefer explicit if/elsif chains for production code. The match can compare against values, arrays, or regexes.
use feature 'switch';
no warnings 'experimental::smartmatch';
my $val = 2;
given ($val) {
when (1) { print "one\n" }
when (2) { print "two\n" }
when ([3, 4]) { print "three or four\n" }
when (/^\d+$/) { print "a number\n" }
default { print "something else\n" }
}
# given/when is experimental; many prefer if/elsif
if ($val == 1) { print "one\n" }
elsif ($val == 2) { print "two\n" }Regular Expressions
Matching (=~)
=~ binds a string to a regex; !~ is negated. Without =~, the regex matches against the default $_ variable. Modifiers like /i (case-insensitive) go after the closing slash.
my $str = "Hello, World";
# =~ binding operator applies a regex
if ($str =~ /World/) {
print "Matched\n";
}
# Negated match
if ($str !~ /foo/) {
print "No foo\n";
}
# Case-insensitive with /i
if ($str =~ /hello/i) {
print "Found hello\n";
}
# Implicit match against $_
for ("cat", "dog", "bat") {
print "$_\n" if /at/; # cat, bat
}Substitution (s///)
s/PATTERN/REPLACEMENT/ substitutes. The /g modifier replaces all occurrences; /e evaluates the replacement as Perl code. tr/// is for character-by-character translation, not regex.
my $str = "Hello, World";
# Replace first occurrence
$str =~ s/World/Perl/; # Hello, Perl
# Replace all (g = global)
my $text = "a-b-c";
$text =~ s/-/_/g; # a_b_c
# Case-insensitive replace
$str =~ s/hello/hi/i;
# Evaluate replacement as code (e)
my $s = "2+3";
$s =~ s/(\d+)\+(\d+)/$1 + $2/e; # 5
# tr/// transliteration (character mapping)
my $dna = "ACGT";
$dna =~ tr/ACGT/TGCA/; # TGCACaptures ($1, named)
Parenthesized groups capture into $1, $2, etc. Named captures (?<name>...) go into %+. Capture variables persist until the next successful match, so copy them if needed later.
my $str = "2024-01-15";
# Numbered capture groups
if ($str =~ /(\d{4})-(\d{2})-(\d{2})/) {
print "Year: $1\n"; # 2024
print "Month: $2\n"; # 01
print "Day: $3\n"; # 15
}
# Named captures (v5.10+)
if ($str =~ /(?<year>\d{4})-(?<month>\d{2})/) {
print $+{year}; # 2024
print $+{month}; # 01
}
# Capture all matches globally
my @nums = ($str =~ /(\d+)/g); # (2024, 01, 15)Quantifiers & Anchors
Quantifiers are greedy by default; add ? for lazy matching (*?, +?, ??). ^ and $ match line boundaries under /m; \A and \z always match absolute string start/end. \b matches a word boundary.
# Quantifiers
/a/ # exactly one
/a?/ # 0 or 1
/a*/ # 0 or more
/a+/ # 1 or more
/a{3}/ # exactly 3
/a{2,4}/ # 2 to 4
/a{3,}/ # 3 or more
# Greedy vs non-greedy (lazy)
/a.*/ # greedy (matches as much as possible)
/a.*?/ # non-greedy (matches as little as possible)
# Anchors
/^start/ # beginning of string
/end$/ # end of string
/\bword\b/ # word boundary
/\A/ # absolute start of string
/\z/ # absolute end of stringCharacter Classes
Character classes [...] match one character from a set. ^ at the start negates the class. Shorthand classes (\d \w \s) and POSIX classes ([:alpha:]) cover common cases. . matches any char except newline.
# Predefined classes
/\d/ # digit [0-9]
/\D/ # non-digit
/\w/ # word char [a-zA-Z0-9_]
/\W/ # non-word char
/\s/ # whitespace
/\S/ # non-whitespace
/./ # any char except newline
# Custom classes
/[aeiou]/ # any vowel
/[^0-9]/ # NOT a digit
/[a-z]/i # case-insensitive range
# POSIX classes
/[[:alpha:]]/ # letters
/[[:digit:]]/ # digits
/[[:space:]]/ # whitespace
/[[:upper:]]/ # uppercaseModifiers
Modifiers change matching behavior: /i case-insensitive, /g global, /m multiline, /s single-line, /x extended (allows whitespace/comments). qr// compiles a regex once for reuse, improving performance.
# Common modifiers
/abc/i # case-insensitive
/abc/g # global (all matches)
/abc/m # multiline (^/$ match line boundaries)
/abc/s # single-line (. matches newline)
/abc/x # extended (whitespace and comments)
# Extended mode for readability
my $re = qr{
^\d{4} # year
-
\d{2} # month
-
\d{2} # day
}x;
# qr// pre-compiles a regex
my $pattern = qr/\bword\b/i;
if ($str =~ $pattern) { ... }String Operations
length & substr
length returns the number of characters. substr extracts or replaces a substring; negative offsets count from the end. The 4-arg form (or lvalue form) modifies the string in place.
my $s = "Hello, World";
# length
print length($s); # 12
# substr STRING, OFFSET, LENGTH, REPLACEMENT
print substr($s, 0, 5); # Hello
print substr($s, 7); # World
print substr($s, -5); # World (negative offset)
# 4-argument substr (replace in place)
my $str = "Hello, World";
substr($str, 0, 5) = "Hi"; # Hi, World
# Lvalue form modifies original
substr($s, 0, 1) = "J";index & rindex
index finds the first occurrence of a substring (optionally from a position); rindex finds the last. Both return -1 if not found. Use them instead of regex for plain substring searches (faster).
my $s = "Hello, World";
# index STRING, SUBSTR, POSITION
my $pos = index($s, "World"); # 7
my $pos2 = index($s, "o"); # 4 (first)
my $pos3 = index($s, "o", 5); # 8 (from position 5)
my $not = index($s, "xyz"); # -1 (not found)
# rindex searches from the end
my $last = rindex($s, "o"); # 8
# Count occurrences of 'l'
my $count = 0;
my $p = 0;
while (($p = index($s, "l", $p)) >= 0) {
$count++;
$p++;
}chomp & chop
chomp removes only the input record separator (usually \n) and is safe; chop blindly removes the last character. Always use chomp when reading lines. Both work on arrays and return useful values.
my $line = "Hello\n";
# chomp removes the line terminator (safe)
chomp $line; # $line = "Hello", returns 1
# chomp a whole array at once
my @lines = ("a\n", "b\n", "c\n");
chomp @lines; # removes \n from all
# chop removes the last character (any char, risky)
my $s = "abc";
chop $s; # "ab"
# chomp returns the count removed
my $cnt = chomp(my $x = "test\n"); # $cnt = 1
# chop returns the removed character
my $c = chop my $y = "xyz"; # $c = 'z'split & join
split breaks a string into a list using a regex; a LIMIT caps the number of pieces. join glues a list together with a separator (a plain string, not a regex). They are inverse operations for simple cases.
# split /PATTERN/, EXPR, LIMIT
my @parts = split /,/, "a,b,c"; # (a, b, c)
my @lim = split /,/, "a,b,c", 2; # (a, "b,c")
# Split on whitespace
my @words = split /\s+/, "one two three";
# Split into characters
my @chars = split //, "abc"; # (a, b, c)
# join SEPARATOR, LIST
my $csv = join(",", "a", "b", "c"); # a,b,c
my $str = join(" - ", @parts);
# Empty separator joins without delimiter
my $joined = join("", @chars); # abcCase Conversion
lc/uc/lcfirst/ucfirst are functions. \L, \U, \u, \l are in-string case modifiers inside double quotes, terminated by \E. Useful for formatting interpolated strings.
my $s = "Hello World";
print lc($s); # hello world
print uc($s); # HELLO WORLD
print lcfirst($s); # hello World
print ucfirst($s); # Hello World (already capped)
# In-string case modifiers (\L \U \u \l \E)
my $name = "Alice";
print "\L$name\E"; # alice (\L starts, \E ends)
print "\U$name\E"; # ALICE
print "\u$name"; # Alice (capitalize first char)
print "\l$name"; # alice (lowercase first char)
# Title-case each word via regex
$s =~ s/(\w+)/\u\L$1/g; # Hello Worldsprintf & printf
printf prints formatted text; sprintf returns it. Format specifiers: %s string, %d integer, %f float, %x hex, %o octal, %b binary. Width and precision control padding: %5d, %05d, %.2f.
# printf FORMAT, LIST (prints)
printf "Name: %s, Age: %d\n", "Alice", 30;
printf "Pi: %.2f\n", 3.14159; # 3.14
printf "%5d\n", 42; # ' 42' (right-aligned)
printf "%-5d|\n", 42; # '42 |' (left-aligned)
printf "%05d\n", 42; # '00042' (zero-padded)
# sprintf returns the formatted string
my $date = sprintf("%04d-%02d-%02d", 2024, 1, 15);
# "2024-01-15"
# Common specifiers: %s %d %f %x %o %b %%
printf "Hex: %x, Oct: %o, Bin: %b\n", 255, 8, 5;Subroutines
Defining Subs
Arguments arrive in the @_ array. shift (with no argument) takes from @_ inside a sub. Unpacking with my ($a, $b) = @_ is idiomatic. Subs return the last evaluated expression if no return.
sub greet {
my $name = shift; # default: shifts from @_
print "Hello, $name!\n";
}
greet("Alice"); # Hello, Alice!
# Subs are global by default
sub add {
my ($a, $b) = @_; # unpack arguments
return $a + $b;
}
print add(2, 3); # 5
# Forward declaration (define later)
sub compute;
print compute(4);Arguments (@_)
@_ holds all arguments. Flatten arrays/hashes lose their identity when passed (they become a flat list), so pass references for nested structures. Named args (a hash) are flexible and self-documenting.
sub sum {
my $total = 0;
$total += $_ for @_; # iterate all args
return $total;
}
print sum(1, 2, 3, 4); # 10
# Named arguments via hash
sub info {
my %args = @_;
print "Name: $args{name}\n";
print "Age: $args{age}\n";
}
info(name => "Alice", age => 30);
# Pass arrays/hashes by reference
sub total {
my $arr = shift;
my $sum = 0;
$sum += $_ for @$arr;
return $sum;
}Return Values
subs can return scalars, lists, or references. Returning a list lets callers destructure it. Without an explicit return, the last expression is returned (in the caller's context).
# Single return value
sub square { return $_[0] ** 2; }
# Return a list
sub min_max {
my @nums = @_;
my ($min, $max) = ($nums[0], $nums[0]);
for (@nums) {
$min = $_ if $_ < $min;
$max = $_ if $_ > $max;
}
return ($min, $max);
}
my ($lo, $hi) = min_max(3, 1, 4, 1, 5);
# Implicit return (last expression)
sub name { "Alice" } # returns "Alice"
# Void context (no return needed)
sub log_msg { print "log: $_[0]\n"; }Context & wantarray
wantarray returns true in list context, false (but defined) in scalar context, and undef in void context. This lets a sub behave differently based on how its return value is used (a powerful Perl idiom).
sub get_data {
if (wantarray) {
return (1, 2, 3); # list context
} elsif (defined wantarray) {
return "scalar"; # scalar context
} else {
return; # void context
}
}
my @list = get_data(); # (1, 2, 3)
my $scalar = get_data(); # "scalar"
get_data(); # void
# localtime is context-sensitive
my $t = localtime(); # scalar: "Mon Jan 1 12:00:00 2024"
my @t = localtime(); # list: (sec, min, hour, mday, ...)
# scalar() forces scalar context
print scalar(localtime());Anonymous Subs
Anonymous subs (sub { ... }) create coderefs, first-class values you can store, pass, and call. Closures capture lexical variables from their enclosing scope and keep them alive, enabling stateful callbacks.
# Anonymous sub (coderef)
my $greet = sub {
my $name = shift;
return "Hi, $name";
};
print $greet->("Alice"); # Hi, Alice (preferred)
print &$greet("Bob"); # older syntax
# Closure: captures outer variable
sub make_counter {
my $count = 0;
return sub { return ++$count; };
}
my $c = make_counter();
print $c->(); # 1
print $c->(); # 2 (count persists)
# Pass subs as arguments
sub apply { my ($f, $x) = @_; return $f->($x); }
print apply(sub { $_ * 2 }, 21); # 42Named Arguments & Defaults
Collect named args into a hash and apply defaults with //= (defined-or). You can mix positional and named args by putting the hash last. Returning a reference is common for option bundles.
sub create_user {
my %args = @_;
# Apply defaults with defined-or
$args{role} //= 'user';
$args{active} //= 1;
return \%args;
}
my $user = create_user(
name => 'Alice',
email => '[email protected]',
);
print $user->{role}; # user (default applied)
# Mix positional + named (named must come last)
sub point {
my ($x, $y, %opts) = @_;
print "($x, $y) color=$opts{color}\n";
}
point(1, 2, color => 'red');File I/O
Opening Files
Always use the 3-argument open with a lexical filehandle. The mode is the middle argument: < read, > write (truncate), >> append. Check with 'or die' and include $! for the system error message.
# 3-argument open (recommended)
open(my $fh, '<', 'input.txt')
or die "Cannot open input.txt: $!";
# Read the whole file
my @lines = <$fh>;
close $fh;
# Modes: < read, > write (truncate), >> append,
# +< read/write, +> read/write (truncate)
open(my $out, '>', 'output.txt')
or die "Cannot write: $!";
open(my $app, '>>', 'log.txt')
or die "Cannot append: $!";Reading Files
Iterate line by line for large files (memory efficient). To slurp a whole file, localize $/ (the record separator) and read once. $. tracks the current line number across reads.
open(my $fh, '<', 'file.txt') or die $!;
# Line by line (memory efficient)
while (my $line = <$fh>) {
chomp $line;
print "Read: $line\n";
}
# Slurp entire file (undef the record separator)
local $/;
my $content = <$fh>;
# Read all lines into an array
my @lines = <$fh>;
close $fh;
# $. holds the current line number
open(my $f2, '<', 'file.txt') or die $!;
while (<$f2>) { print "$.: $_"; }Writing Files
print and printf take the filehandle as the first argument without a comma. Output is buffered; enable autoflush ($| = 1) for immediate writes (useful for logs and pipes). close() flushes automatically.
open(my $fh, '>', 'out.txt') or die $!;
print $fh "Hello, File!\n";
print $fh "Second line\n";
# Formatted write
printf $fh "%d: %s\n", 1, "first";
# Autoflush (no buffering)
use IO::Handle;
$fh->autoflush(1);
# Or via select
my $old = select($fh); $| = 1; select($old);
close $fh; # flushes and closesDiamond Operator
The diamond operator <> reads from files listed in @ARGV, or STDIN if @ARGV is empty. Combined with -i / $^I it powers in-place editing like sed -i. $ARGV holds the current filename.
# <> reads from files in @ARGV, or STDIN if none
while (<>) {
print "$.: $_";
}
# Equivalent to: while (my $line = <ARGV>) { ... }
# Read all of stdin (or all arg files)
my @input = <>;
# Specify files programmatically
@ARGV = ('file1.txt', 'file2.txt');
while (<>) {
chomp;
print ">> $_\n";
}
# In-place editing
$^I = '.bak'; # backup extension
while (<>) { s/foo/bar/g; print; }File Handles
Prefer lexical filehandles (my $fh) which auto-close when they go out of scope. Bareword handles are global and can clash. The three standard handles STDIN/STDOUT/STDERR are pre-opened.
# Lexical filehandle (modern, recommended)
open(my $fh, '<', $file) or die $!;
# Bareword filehandle (legacy, avoid)
open(IN, '<', $file) or die $!;
my $line = <IN>;
close IN;
# Standard handles: STDIN, STDOUT, STDERR
print STDOUT "to stdout\n";
my $err = <STDIN>;
warn "to stderr\n"; # STDERR is unbuffered
# Duplicate a handle
open(my $log, '>&', STDERR) or die $!;
# Open a filehandle to a string (in-memory)
open(my $mem, '>', \my $buffer) or die $!;
print $mem "stored\n";File::Slurper
File::Slurper is the modern, efficient way to read/write whole files. It handles encodings and binmode correctly, unlike hand-rolled slurp code. Prefer it over do { local $/; <$fh> } in new code.
use File::Slurper qw(read_text write_text append_text read_lines);
# Read entire file as a string
my $text = read_text('file.txt');
# Write a string to a file
write_text('out.txt', "Hello\n");
# Append to a file
append_text('log.txt', "New entry\n");
# Read all lines (without newlines by default)
my @lines = read_lines('data.csv');
# Binary files
use File::Slurper qw(read_binary write_binary);
my $bytes = read_binary('image.png');
write_binary('copy.png', $bytes);Directory Operations
opendir/readdir/closedir
opendir/readdir/closedir mirror open/readline/close for directories. readdir returns all entries including . and .. (filter them with /^\.\.?$/). Test each entry with file test operators for type.
opendir(my $dh, '.') or die "Cannot open dir: $!";
while (my $entry = readdir($dh)) {
next if $entry =~ /^\./; # skip dotfiles
print "$entry\n";
}
# Or get all entries at once
rewinddir($dh);
my @entries = readdir($dh);
closedir $dh;
# Filter to only subdirectories
opendir(my $d, '.') or die $!;
my @dirs = grep { -d $_ } readdir($d);
closedir $d;glob
glob expands shell-style wildcards (* ? []) and returns matching filenames. The <pattern> form looks like readline but triggers globbing for wildcard patterns. File::Glob provides bsd_glob with extra flags.
# glob expands filename wildcards
my @files = glob('*.txt');
my @perl = glob('*.pl *.pm');
# Angle-bracket form (legacy)
my @same = <*.txt>;
# With a path
my @logs = glob('/var/log/*.log');
# Recursive globbing
use File::Glob qw(:bsd_glob);
my @all = bsd_glob('**/*.pm'); # needs :bsd_glob
# Capture flags: GLOB_ERR, GLOB_MARK (trailing / on dirs)
my @marked = bsd_glob('*', GLOB_MARK);mkdir/rmdir/chdir
mkdir/rmdir handle single directories (rmdir requires empty). File::Path's make_path/remove_tree handle nested structures. chdir changes the working directory; use Cwd::getcwd to read it.
# Create a directory (default perms modified by umask)
mkdir 'newdir' or die $!;
mkdir 'newdir', 0755; # explicit permissions
# Create nested directories
use File::Path qw(make_path remove_tree);
make_path('a/b/c');
make_path('x/y', { mode => 0755 });
# Remove a directory (must be empty)
rmdir 'emptydir' or die $!;
# Remove recursively
remove_tree('a');
# Change working directory
chdir '/tmp' or die $!;
# Get current directory
use Cwd qw(getcwd);
print getcwd();File::Find
File::Find recursively walks a directory tree. Inside the wanted subroutine, $_ is the basename and $File::Find::name is the full path. finddepth does post-order traversal (useful for deletions).
use File::Find;
my @found;
find(sub {
return if -d; # skip directories
push @found, $File::Find::name
if /\.pm\z/;
}, '.');
# finddepth visits directories after their contents
finddepth(sub {
unlink $_ if -f and -M $_ > 30; # delete old files
}, '/tmp/old');
# Multiple starting directories
find(\&wanted, qw(src lib t));
sub wanted {
my $path = $File::Find::name;
print "$path\n" if -f $path;
}Path::Tiny
Path::Tiny is a modern, ergonomic module for file and path operations, replacing many File::Spec/File::Path idioms. Methods chain naturally and handle errors with helpful messages.
use Path::Tiny;
# Create a path object
my $path = path('/tmp/test.txt');
# File operations
$path->touch;
$path->spew("content\n");
my $content = $path->slurp;
my @lines = $path->lines;
# Path manipulations
my $parent = $path->parent;
my $child = $path->child('sub.txt');
my $abs = $path->absolute;
my $rel = $abs->relative('/tmp');
# Iterate directory contents
for my $f (path('.')->children) {
print $f, "\n" if $f->is_file;
}File::Copy & unlink
File::Copy provides copy and move across filesystems. File::Spec builds portable paths without hardcoding separators. unlink deletes files (returns the count removed); it cannot delete directories.
use File::Copy qw(copy move);
# Copy a file
copy('src.txt', 'dst.txt') or die $!;
copy('src.txt', 'dst.txt', 8192); # buffer size
# Move / rename
move('old.txt', 'new.txt') or die $!;
# Portable paths via File::Spec
use File::Spec;
my $path = File::Spec->catfile('dir', 'file.txt');
my ($vol, $dir, $file) = File::Spec->splitpath($path);
# Delete files
unlink qw(a.txt b.txt c.txt);
my $removed = unlink grep { -f } glob('*.tmp');References
Creating References
The backslash operator creates a reference to an existing variable. ref() returns the type (SCALAR, ARRAY, HASH, CODE, ...) or an empty string for non-references. Blessed references return the class name.
my $scalar = 42;
my @arr = (1, 2, 3);
my %h = (a => 1);
# Backslash creates a reference
my $sref = \$scalar;
my $aref = \@arr;
my $href = \%h;
my $cref = \&some_sub;
# Check what a reference points to
print ref($aref); # ARRAY
print ref($href); # HASH
print ref($sref); # SCALAR
print ref($cref); # CODE
# A non-reference returns ''
print ref("hi"); # (empty string)Dereferencing
Prefix a reference with its sigil (@, %, $) to dereference the whole thing, or use the arrow operator -> for single elements. The block form @{ ... } helps when the reference is an expression.
my $aref = [1, 2, 3];
my $href = { a => 1, b => 2 };
# Whole array
my @arr = @$aref; # or @{ $aref }
my $len = scalar @$aref;
# Single element via arrow
print $aref->[0]; # 1
print $#$aref; # last index (2)
# Whole hash
my %h = %$href;
my @keys = keys %$href;
# Single value via arrow
print $href->{a}; # 1
# Block form for clarity
my @arr2 = @{ $aref };Anonymous Arrays & Hashes
[ ] creates an anonymous array ref, { } a hash ref, and sub { } a code ref. These build complex nested data structures directly without intermediate named variables.
# Anonymous array reference: [ ]
my $arr = [1, 2, 3];
print $arr->[0]; # 1
# Anonymous hash reference: { }
my $h = { name => 'Alice', age => 30 };
print $h->{name};
# Nested anonymous structures
my $data = {
users => [ { name => 'a' }, { name => 'b' } ],
count => 2,
};
# Anonymous subroutine
my $code = sub { print "hi\n" };Nested Structures
References let you build arbitrarily nested structures. Between two subscripts the arrow is optional: $arr->[0][1] equals $arr->[0]->[1]. Auto-vivification creates intermediate references on assignment.
# Array of arrays (2D)
my $matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
];
print $matrix->[1][2]; # 6 (second arrow optional)
# Hash of hashes
my $config = {
db => { host => 'localhost', port => 3306 },
app => { name => 'MyApp', debug => 1 },
};
print $config->{db}{host}; # localhost
# Auto-vivification creates intermediate refs
my %tree;
$tree{a}{b}{c} = 1; # builds the path automaticallyArrow Operator
The arrow -> dereferences: $ref->[idx] for arrays, $ref->{key} for hashes, $ref->() for code, $obj->method for objects. Between consecutive subscripts the arrow is optional for readability.
my $arr = [[1, 2], [3, 4]];
my $h = { users => [{ name => 'a' }] };
# Arrow between subscripts (second is optional)
print $arr->[0][1]; # 2
print $h->{users}[0]{name}; # a
# Arrow for coderef calls
my $greet = sub { "hi $_[0]" };
print $greet->("Bob"); # hi Bob
# Method calls on objects
$obj->method($arg);
# Slicing a dereferenced array
my @vals = @{ $h->{users} };Reference Types
ref() returns the class name for blessed references (objects), which can hide the underlying type. Scalar::Util::reftype() always returns the true structural type. Use weaken() to break reference cycles.
use Scalar::Util qw(reftype weaken);
my $aref = [];
my $href = {};
my $cref = sub {};
print ref($aref); # ARRAY
print reftype($aref); # ARRAY
# A blessed reference (object)
my $obj = bless {}, 'MyClass';
print ref($obj); # MyClass
print reftype($obj); # HASH (the underlying type)
# Type checks
if (ref($r) eq 'ARRAY') { ... }
if (ref($r) eq 'HASH') { ... }
# weaken avoids circular-reference memory leaks
weaken($r);Modules
use vs require
use runs at compile time and calls import() (so exported names are available). require runs at runtime and does not import. Use require for conditional or late loading, and use for the normal case.
# use: runs at compile time, calls the module's import()
use strict;
use warnings;
use Data::Dumper;
# require: runs at runtime, does NOT call import()
require MyModule;
# Conditional loading (runtime)
if ($DEBUG) {
require Carp;
Carp->import('confess');
}
# use with version and import list
use v5.36;
use List::Util 1.33 qw(first sum max);
# use MODULE VERSION LIST
use Data::Dumper qw(Dumper);use lib
@INC is the list of directories Perl searches for modules. 'use lib' prepends directories at compile time. FindBin finds the script's directory so you can locate modules shipped alongside it.
# Add directories to the module search path
use lib '/home/me/lib';
use lib 'lib', 'local/lib';
# Locate a path relative to the script
use FindBin;
use lib "$FindBin::Bin/lib";
# Inspect the search path
print join("\n", @INC);
# Add at runtime (then require)
push @INC, '/path/to/lib';
require MyModule;
# Remove an entry
@INC = grep { !/local/ } @INC;Exporter
Exporter controls what a module exposes. @EXPORT forces exports (often discouraged); @EXPORT_OK lets callers opt in (preferred). %EXPORT_TAGS groups exports for convenient import like ':all'.
package MyModule;
use parent 'Exporter';
our @EXPORT = qw(func1); # exported by default
our @EXPORT_OK = qw(func2 func3); # exported on request
our %EXPORT_TAGS = (
all => [qw(func1 func2 func3)],
);
sub func1 { return "one"; }
sub func2 { return "two"; }
sub func3 { return "three"; }
1; # a module must return a true value
# Usage in another file:
# use MyModule; # gets func1
# use MyModule qw(func2); # gets func2 only
# use MyModule ':all'; # gets all threeBEGIN & END
BEGIN blocks run at compile time (useful for setup that must happen before the rest compiles). END blocks run at program exit (for cleanup). CHECK and INIT run between compile and run time.
# BEGIN runs at compile time
BEGIN {
print "Compiled\n";
require MyModule;
}
# END runs at exit (in LIFO order)
END {
print "Cleaning up\n";
}
# Other phases: UNITCHECK, CHECK, INIT
INIT { print "Running\n"; }
# Compile-time constant definition
use constant DEBUG => 1;
# BEGIN to push a custom lib path early
BEGIN { push @INC, '/custom' }package
package declares a namespace. A module file (MyModule.pm) usually contains one package matching its name. The file must end with a true value (1;) so 'use' knows it loaded successfully.
package MyModule;
use strict;
use warnings;
our $VERSION = '1.00';
sub new {
my $class = shift;
return bless {}, $class;
}
sub method {
my $self = shift;
return "called";
}
1; # required: modules must return a true value
# Switch back to the main package
package main;
my $obj = MyModule->new;
print $obj->method;Constants
use constant creates inlinable constants (no sigil). Readonly and Const::Fast offer lexically-scoped immutable scalars/arrays/hashes. Constants are really subs with a () prototype, allowing them to be inlined.
# use constant (compile-time)
use constant {
PI => 3.14159,
MAX => 100,
DEBUG => 0,
};
print PI; # 3.14159
# Readonly (lexically scoped, immutable)
use Readonly;
Readonly my $NAME => 'Alice';
# Const::Fast
use Const::Fast;
const my $count => 5;
# A constant is just a sub with an empty prototype
sub MAX_CONNECTIONS () { 10 }
print MAX_CONNECTIONS; # 10Object-Oriented Programming
Package & bless
Perl OOP is explicit: a class is a package, an object is a blessed reference. bless links a reference to a class so method dispatch works. The constructor (conventionally new) builds and blesses the object.
package Animal;
use strict;
use warnings;
sub new {
my ($class, %args) = @_;
my $self = { name => $args{name}, sound => 'generic' };
return bless $self, $class;
}
sub name { shift->{name} }
1;
# bless associates a reference with a package (the class).
# $self is a hash ref blessed into Animal.Constructor (new)
The constructor is conventionally named new but is just a regular sub. The first argument is the class name. Use Class->new(args) (arrow syntax); indirect syntax (new Class) is discouraged as it can misparse.
package Point;
use strict;
use warnings;
sub new {
my ($class, $x, $y) = @_;
my $self = { x => $x, y => $y };
return bless $self, $class;
}
# Usage (arrow syntax preferred)
my $p = Point->new(3, 4);
# Indirect syntax ( discouraged)
my $p2 = new Point(3, 4);
# Named-argument constructor
sub new_named {
my ($class, %args) = @_;
return bless \%args, $class;
}Methods
Methods are subs whose first argument is the invocant ($self for instance methods, the class name for class methods). Use shift to grab it, then unpack the rest. Setters often return $self for chaining.
package Point;
sub new { ... }
# Instance method: first arg is the object
sub distance {
my ($self, $other) = @_;
my $dx = $self->{x} - $other->{x};
my $dy = $self->{y} - $other->{y};
return sqrt($dx**2 + $dy**2);
}
# Getter
sub x { my $self = shift; return $self->{x}; }
# Setter (chainable)
sub set_x {
my ($self, $x) = @_;
$self->{x} = $x;
return $self;
}
# Class method
sub count { our $count; return ++$count }Inheritance (use parent)
use parent sets @ISA cleanly and calls the parent's import. Use SUPER::method to call an overridden parent method. Multiple inheritance is supported but method resolution order (MRO) can be subtle; use mro::get_linear_isa to inspect.
package Dog;
use parent 'Animal';
sub new {
my ($class, %args) = @_;
my $self = $class->SUPER::new(%args); # call parent
$self->{breed} = $args{breed};
return $self;
}
sub speak {
my $self = shift;
return "Woof!";
}
# Direct @ISA (lower-level)
package Cat;
our @ISA = ('Animal');
# Multiple inheritance
package Duck;
use parent qw(Flyable Animal);Accessors
You can write accessors by hand (one sub handles both get and set). Modules like Class::Accessor, Class::Tiny, Moo, or Moose generate them, reducing boilerplate. The choice depends on your dependency budget.
# Hand-written accessor (getter + setter)
sub name {
my $self = shift;
if (@_) { $self->{name} = shift; return $self; }
return $self->{name};
}
# Class::Accessor generates them
package User;
use parent 'Class::Accessor';
__PACKAGE__->mk_accessors(qw(name email age));
# Usage
my $u = User->new({ name => 'Alice' });
$u->name; # get
$u->name('Bob'); # set
# Moo / Moose generate accessors declaratively
has name => (is => 'rw');DESTROY & AUTOLOAD
DESTROY is called automatically when an object's refcount hits zero (use for cleanup like closing handles). AUTOLOAD intercepts calls to undefined methods, enabling dynamic accessors or delegation, but use it sparingly.
package FileHandle::Custom;
sub new {
my ($class, $file) = @_;
open(my $fh, '<', $file) or die $!;
return bless { fh => $fh }, $class;
}
sub DESTROY {
my $self = shift;
close $self->{fh} if $self->{fh};
# auto-called when refcount reaches 0
}
# AUTOLOAD catches undefined method calls
sub AUTOLOAD {
my $self = shift;
our $AUTOLOAD;
my $method = $AUTOLOAD;
$method =~ s/.*:://;
return $self->{$method}; # treat as accessor
}File Test Operators
Existence & Type
File test operators (-e, -f, -d, etc.) check attributes in one stat call. Stacking (-f -e $f) shares one stat. The _ operator reuses the result of the most recent stat/lstat/file test for efficiency.
my $f = 'file.txt';
print "exists\n" if -e $f; # exists
print "regular\n" if -f $f; # plain file
print "dir\n" if -d $f; # directory
print "symlink\n" if -l $f; # symbolic link
print "socket\n" if -S $f; # socket
print "pipe\n" if -p $f; # named pipe (FIFO)
print "char dev\n" if -c $f; # character device
print "block dev\n" if -b $f; # block device
# Stack tests (uses the same stat cache)
print "regular file\n" if -f -e $f;
# _ reuses the last stat result
print "size: ", -s _ if -e $f;Permissions (-r/-w/-x)
-r/-w/-x check permissions for the effective user (use -R/-W/-X for the real user). -u/-g/-k detect setuid, setgid, and sticky bits. These reflect the actual filesystem state, not just mode bits.
my $f = 'script.pl';
print "readable\n" if -r $f; # effective uid can read
print "writable\n" if -w $f; # effective uid can write
print "executable\n" if -x $f; # effective uid can exec
print "owned\n" if -o $f; # owned by effective uid
# Real uid variants (rarely needed)
print "real read\n" if -R $f;
print "real write\n" if -W $f;
print "real exec\n" if -X $f;
# setuid / setgid / sticky
print "setuid\n" if -u $f;
print "setgid\n" if -g $f;
print "sticky\n" if -k $f;Size (-s)
-s returns the file size in bytes (or undef). -z tests for zero size; -s in boolean context tests for non-zero. For everything else (uid, gid, times), use stat() which returns a 13-element list.
my $f = 'data.bin';
# -s returns size in bytes (or undef if missing)
my $size = -s $f;
print "Size: $size bytes\n";
# Empty file
if (-z $f) { print "empty\n"; }
# Non-empty (size > 0)
if (-s $f) { print "has data\n"; }
# Human-readable size
use Number::Bytes::Human qw(format_bytes);
print format_bytes(-s $f), "\n";
# stat() for full detail
my @st = stat($f);
print "Size: $st[7] bytes\n"; # index 7 is sizeTime (-M/-A/-C)
-M, -A, -C return the file's age in days relative to $^T (the script's start time), so values are positive for older files. For epoch seconds, use stat() indices 9 (mtime), 8 (atime), 10 (ctime).
my $f = 'file.txt';
# All return age in days since script start ($^T)
print -M $f; # modification age (days)
print -A $f; # access age (days)
print -C $f; # inode-change age (days)
# Older than 30 days?
if (-M $f > 30) { print "old file\n"; }
# Modified today?
if (-M $f < 1) { print "modified today\n"; }
# Epoch seconds via stat
my @st = stat($f);
my $mtime = $st[9]; # mtime (epoch)
my $atime = $st[8]; # atime (epoch)
my $ctime = $st[10]; # ctime (epoch)Text/Binary (-T/-B)
-T/-B guess text vs binary heuristically by examining the first bytes (null bytes, control chars). Empty files count as text. For real MIME types use a module like File::MimeInfo::Magic.
my $f = 'file.txt';
# -T text file, -B binary file
if (-T $f) { print "text\n"; }
if (-B $f) { print "binary\n"; }
# Perl heuristically decides from the first block
# Filter a directory to text files only
opendir(my $dh, '.');
my @text_files = grep { -f && -T } readdir($dh);
closedir $dh;
# Note: empty files count as text
# Proper MIME detection
use File::MimeInfo::Magic;
print mimetype($f);stat & lstat
stat() returns 13 fields; the most used are size (7), mode (2), mtime (9). lstat() is like stat but doesn't follow symlinks (so you can inspect the link itself). The _ operator reuses the cached result for chained tests.
my $f = 'file.txt';
# stat returns a 13-element list
my @s = stat($f);
# 0 dev, 1 ino, 2 mode, 3 nlink, 4 uid, 5 gid,
# 6 rdev, 7 size, 8 atime, 9 mtime, 10 ctime,
# 11 blksize, 12 blocks
my $size = $s[7];
my $perms = sprintf "%04o", $s[2] & 07777;
# lstat does NOT follow symlinks
my @ls = lstat($f);
# stat on an open filehandle
my @sf = stat($fh);
# _ reuses the last stat result (no new syscall)
print -s _ if -e $f;CPAN
cpanm (App::cpanminus)
cpanm (App::cpanminus) is the most popular CPAN client: fast, lightweight, and zero-config. It resolves and installs dependencies automatically. -l installs to a local directory; --installdeps reads a cpanfile.
# Install a module from the command line:
# cpanm DBI
# cpanm Mojolicious
# cpanm --interactive Module::Name
# Install a specific version range
# cpanm DBI~">=1.6"
# Install from a git repository
# cpanm https://github.com/user/Mod.git
# Install dependencies from a cpanfile
# cpanm --installdeps .
# Install into a local directory
# cpanm -l extlib DBI
# Verbose output
# cpanm -v DBIcpanfile
A cpanfile lists dependencies by phase (runtime, test, develop) and relationship (requires, recommends, suggests, conflicts). Tools like cpanm, Carton, and cpm read it to install the right modules.
# cpanfile declares runtime and test dependencies
requires 'perl', '5.014';
requires 'DBI' => '1.6';
requires 'Mojolicious' => '9.0';
# Phase-specific dependencies
on develop => sub {
requires 'Test::Pod';
requires 'Test::Perl::Critic';
};
on test => sub {
requires 'Test::More' => '0.98';
requires 'Test::Exception';
};
recommends 'JSON::XS';
conflicts 'Old::Module';Carton
Carton reads a cpanfile, installs dependencies into local/, and writes a cpanfile.snapshot locking exact versions for reproducible deployments. Use 'carton exec' to run programs with the local environment.
# Carton manages a locked dependency tree
# Install Carton: cpanm Carton
# Commands (shell):
# carton install # install from cpanfile
# carton install DBI # add a module
# carton exec ./app.pl # run with local deps
# carton bundle # vendor tarballs
# carton show # list installed
# cpanfile.snapshot locks exact versions
# local/ holds the installed modules
# In your code, ensure the lib path:
use lib 'local/lib/perl5';
use DBI;Installing Modules
You can install modules anywhere and add that path with 'use lib'. local::lib bootstraps a user-level module directory. Module::Util locates an installed module's path; -M on the CLI loads a module for one-liners.
# Install into a local directory
# cpanm -l extlib DBI
# Then use it in your script:
use lib 'extlib/lib/perl5';
use DBI;
# Bootstrap local::lib
# cpanm --local-lib=~/perl5 local::lib
use local::lib '~/perl5';
# Find a module's installed path
use Module::Util qw(module_path);
my $path = module_path('DBI');
# Check an installed version from the CLI:
# perl -MDBI -e 'print $DBI::VERSION'perlbrew
perlbrew lets you install and switch between multiple Perl versions in your home directory without touching the system Perl. 'perlbrew use' sets the perl for the current shell; 'switch' sets the default.
# App::perlbrew manages multiple perl installations
# Shell commands:
# perlbrew init
# perlbrew install perl-5.38.0
# perlbrew switch perl-5.38.0
# perlbrew use perl-5.36.0
# perlbrew list
# perlbrew install-cpanm
# Install a CPAN module into a specific perl
# perlbrew exec --with perl-5.38 cpanm DBI
# Use a specific perl in a script's shebang:
# #!/home/user/perl5/perlbrew/perls/perl-5.38/bin/perl
use v5.38;
use strict;
use warnings;Local::lib
local::lib sets up environment variables (PATH, PERL5LIB, etc.) so modules install into a private directory without root. Use 'use local::lib' in scripts or eval the bootstrap line in your shell rc file.
# Bootstrap local::lib (shell):
# cpanm --local-lib=~/perl5 local::lib
# In your script, use the local library:
use local::lib;
# Or specify a path explicitly
use local::lib '/path/to/libs';
# Set up environment in your shell rc:
# eval "$(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib)"
# Verify @INC includes your local path
print "$_\n" for @INC;
# Now install modules locally:
# cpanm -l ~/perl5 Some::ModuleException Handling
die
die throws an exception that propagates up the call stack. Include $! for system errors. A trailing \n suppresses the automatic 'at file line N' suffix. die with a reference throws an object exception.
# die throws an exception
open(my $fh, '<', 'missing.txt')
or die "Cannot open: $!"; # $! is the system error
# A trailing newline suppresses "at file line N"
die "Custom error\n";
# die with a reference (object exception)
die MyException->new(message => 'oops');
# Default die appends location info
die "Something bad happened";
# prints: Something bad happened at script.pl line 3.warn
warn prints a message to STDERR without dying. Disable specific warning categories with 'no warnings'. The %SIG{__WARN__} hook lets you intercept, log, or suppress warnings globally.
# warn prints to STDERR but continues
warn "This is a warning: $!";
# Trailing newline omits location
warn "Just a note\n";
# Disable a warning category locally
{
no warnings 'uninitialized';
print $undefined; # no warning
}
# Custom __WARN__ handler
$SIG{__WARN__} = sub {
my $msg = shift;
log_warning($msg);
print STDERR $msg;
};eval
eval BLOCK catches exceptions thrown inside it (die becomes a non-fatal error captured in $@). Prefer eval BLOCK over eval STRING, which compiles a string at runtime and is a security risk with untrusted input.
# eval BLOCK (modern, recommended)
eval {
risky_operation();
die "failed";
};
if ($@) {
print "Caught: $@\n";
}
# eval STRING parses and runs code (avoid; slow & unsafe)
eval '\$x = 1 + 2';
# eval returns the last expression on success
my $result = eval { compute() };
my $error = $@; # check immediately
# Always check $@ right after eval
# (or use Try::Tiny for safety)$@ & $!
$@ holds the last eval/die error. $! is errno (dualvar: number in numeric context, string in string context). $? is child exit status; $? >> 8 is the exit code, $? & 127 the signal. $^E is platform-specific.
# $@ - the last eval error
eval { die "boom" };
print $@ if $@;
# $! - errno (system errors)
open(my $fh, '<', 'x') or die "open: $!";
# $! in numeric vs string context
print int($!); # errno number (e.g. 2)
print "$!"; # error string (e.g. "No such file")
# $? - child process status
system("ls");
print $? >> 8; # exit code
# $^E - extended error (platform-specific)
print $^E;Try::Tiny
Try::Tiny provides clean try/catch/finally syntax and fixes $@ clobbering issues that plain eval has. Inside the catch block the error is in $_ (not $@). finally runs regardless of success or failure.
use Try::Tiny;
try {
risky_code();
die "explicit fail";
} catch {
warn "caught: $_"; # $_ is the error, not $@
} finally {
cleanup(); # always runs
};
# Return value flows through
my $val = try {
compute_value();
} catch {
default_value();
};
# Note: inside catch use $_, not $@Custom Exceptions
die can throw a reference (object) instead of a string. Overload stringification so the object prints nicely. Check the type with ref/isa. Exception::Class provides a fuller exception hierarchy with fields.
package MyException;
use overload '""' => sub { $_[0]->{message} };
sub new {
my ($class, %args) = @_;
return bless { message => $args{message} }, $class;
}
1;
# Usage
eval {
die MyException->new(message => 'Custom error');
};
if (ref($@) && ref($@)->isa('MyException')) {
print "Custom: $@\n";
}
# Or use Exception::Class for richer features
use Exception::Class ('MyEx' => { fields => ['detail'] });
MyEx->throw(detail => 'something');Database (DBI)
Connecting (DBI)
DBI is Perl's database interface, with separate DBD:: drivers per engine. Use RaiseError to auto-die on errors. The DSN starts with 'DBI:driver:...'. For SQLite the DSN is just 'DBI:SQLite:dbname=file'.
use DBI;
my $dbh = DBI->connect(
'DBI:mysql:database=test;host=localhost',
'user', 'password',
{ RaiseError => 1, AutoCommit => 1 }
) or die "Connect failed: $DBI::errstr";
# Always disconnect when done
$dbh->disconnect;
# Other drivers
# DBI:mysql: DBI:Pg: DBI:SQLite: DBI:Oracle:
my $sqlite = DBI->connect('DBI:SQLite:dbname=test.db');
Queries (prepare/execute)
prepare + execute is the safe pattern: prepare once, execute many times with different placeholders. fetchrow_array/fetchrow_hashref iterate rows; fetchall_arrayref returns them all. Call finish() to release early.
my $sth = $dbh->prepare('SELECT id, name FROM users WHERE age > ?');
$sth->execute(18);
# Fetch one row at a time as an array
while (my @row = $sth->fetchrow_array) {
print "id=$row[0], name=$row[1]\n";
}
# Fetch as a hashref
while (my $row = $sth->fetchrow_hashref) {
print $row->{name}, "\n";
}
# Fetch all rows
my $rows = $sth->fetchall_arrayref;
$sth->finish; # release statement handle earlyFetching Results
DBI convenience methods combine prepare+execute+fetch: selectall_arrayref (all rows), selectrow_array (one value), selectrow_hashref (one row). They're shorter but less efficient for repeated calls than prepare/execute.
# All rows as an array of arrays
my $rows = $dbh->selectall_arrayref('SELECT * FROM users');
# All rows keyed by a column
my $by_id = $dbh->selectall_hashref(
'SELECT * FROM users', 'id'
);
# Single value
my $count = $dbh->selectrow_array('SELECT COUNT(*) FROM users');
# Single row as a hash
my $row = $dbh->selectrow_hashref(
'SELECT * FROM users WHERE id = ?', undef, 5
);Transactions
Transactions require AutoCommit off. Wrap changes in eval and call commit() on success or rollback() on error. begin_work temporarily disables AutoCommit for one transaction and resets it afterwards.
# Disable AutoCommit to begin a transaction
$dbh->{AutoCommit} = 0;
eval {
$dbh->do('INSERT INTO accounts ...');
$dbh->do('UPDATE accounts SET balance = ...');
$dbh->commit;
};
if ($@) {
$dbh->rollback;
die "Transaction failed: $@";
}
# begin_work is cleaner (auto-resets AutoCommit)
$dbh->begin_work;
eval { ...; $dbh->commit; };
$dbh->rollback if $@;Placeholders
Placeholders (?) separate SQL structure from data, preventing SQL injection and allowing statement reuse. Never interpolate values into SQL strings. quote() escapes a value but placeholders are cleaner and often faster.
# ALWAYS use placeholders to prevent SQL injection
my $sth = $dbh->prepare(
'INSERT INTO users (name, age) VALUES (?, ?)'
);
$sth->execute('Alice', 30);
$sth->execute('Bob', 25);
# Named placeholders (some drivers)
# :name, :age
$dbh->do('INSERT ... VALUES (:name, :age)', undef,
{ name => 'Alice', age => 30 });
# Bulk insert
my @rows = (['a', 1], ['b', 2]);
$sth->execute(@$_) for @rows;
# quote() exists but prefer placeholders
my $q = $dbh->quote("O'Brien");Error Handling
With RaiseError enabled, DBI dies on errors so you can use eval/Try::Tiny. Without it, check return values and inspect $dbh->err/errstr. HandleError lets you intercept errors for logging or recovery.
# RaiseError: die on error (recommended)
my $dbh = DBI->connect($dsn, $u, $p,
{ RaiseError => 1, PrintError => 0 });
# Manual checking (without RaiseError)
my $rv = $dbh->do('UPDATE ...');
unless ($rv) { die $dbh->errstr; }
# Error info: $dbh->err, errstr, state
print $dbh->err; # error code
print $dbh->errstr; # error message
print $dbh->state; # SQLSTATE
# HandleError callback for custom handling
$dbh->{HandleError} = sub {
my $msg = shift;
log_error($msg);
return 0; # 0 = propagate (die)
};Command Line & @ARGV
@ARGV
@ARGV contains the command-line arguments to the script (the script name is in $0, not @ARGV). You can shift, pop, or index it like any array. Many modules (Getopt::Long) consume @ARGV for you.
# @ARGV holds command-line args (script name NOT included)
my $file = $ARGV[0];
my @args = @ARGV;
# Number of arguments
my $n = scalar @ARGV;
# Process args one at a time
while (@ARGV) {
my $arg = shift @ARGV;
print "Arg: $arg\n";
}
# Capture all remaining args
my @rest = @ARGV;
# The script's own name is in $0
print "Running $0\n";Getopt::Long
Getopt::Long parses command-line options with type specifiers: =s string, =i integer, =f float, @ for multiple. Short/long aliases use | (verbose|v). Options are removed from @ARGV; remaining args stay.
use Getopt::Long;
my ($verbose, $name, $count, @files, $help);
GetOptions(
'verbose|v' => \$verbose,
'name=s' => \$name,
'count=i' => \$count,
'files=s@' => \@files,
'help|h' => \$help,
) or die "Usage: $0 [--verbose] [--name=X]\n";
exit print_usage() if $help;
print "verbose=$verbose name=$name count=$count\n";
# Run: perl script.pl -v --name=Alice --count=3 file1 file2Diamond Operator
The diamond operator <> reads through the files named in @ARGV (or STDIN if @ARGV is empty), letting one script work as a Unix filter. With $^I set it edits files in place, keeping backups.
# <> reads from files in @ARGV, or STDIN if empty
while (my $line = <>) {
chomp $line;
print "processed: $line\n";
}
# Run: perl script.pl file1.txt file2.txt
# Run: cat file | perl script.pl
# $ARGV is the current filename being read
# @ARGV is consumed as files are processed
# Reset to read a new set of files
@ARGV = @new_files;
while (<>) { ... }
# In-place edit with backup
$^I = '.bak';
while (<>) { s/foo/bar/g; print; }Exit Codes
exit terminates the program with a status code (0 = success to shells). $? holds child status after system/backticks: bits 0-6 are the signal, bit 7 is core dump, bits 8+ are the exit code.
# exit with a status (0 = success)
exit 0; # success
exit 1; # general error
exit 255; # out-of-range wraps (255 == -1)
# die sets a non-zero exit
eval { die "error\n" };
exit 1 if $@;
# Check a child's exit status
system("ls");
my $code = $? >> 8; # exit value
my $signal = $? & 127; # signal that killed it
my $core = ($? & 128); # core dump flag
print "exit=$code signal=$signal\n";
# END blocks still run on exit
END { cleanup(); }__DATA__
__DATA__ / __END__ separates code from data within a single file; the DATA filehandle reads what follows. Useful for embedding fixtures, templates, or configuration. In modules, use __DATA__ so callers can read it.
# __DATA__ (or __END__) marks the end of code
# <DATA> reads from this section in the same file
while (my $line = <DATA>) {
chomp $line;
print "[$line]\n";
}
__DATA__
line 1
line 2
line 3
# __END__ behaves the same in a script;
# in a module use __DATA__ so the section is accessible.
# Data::Section supports multiple named sections.One-liners
Perl is famous for one-liners via flags: -e code, -n/-p loop over input, -i in-place edit, -a autosplit into @F, -F delimiter, -l auto-chomp/newline. These turn Perl into a powerful sed/awk replacement.
# -e execute code
# perl -e 'print "hello\n"'
# -n loop over input (no auto-print)
# perl -ne 'print if /pattern/' file.txt
# -p loop and auto-print (like sed)
# perl -pe 's/foo/bar/g' file.txt
# -i in-place edit (with optional backup suffix)
# perl -i.bak -pe 's/old/new/g' *.txt
# -l auto chomp + add newline on print
# -a autosplit into @F (default whitespace)
# -F set the delimiter for -a
# perl -F, -ane 'print "$F[1]\n"' data.csvFormatted Output
printf
printf prints formatted text. Specifiers: %s string, %d int, %f float, %x/%o/%b hex/octal/binary, %e scientific. Flags: - left-align, + force sign, 0 zero-pad, # alternate form. Width and precision: %8.2f.
# printf FORMAT, LIST
printf "Name: %s\n", "Alice";
printf "Age: %d\n", 30;
printf "Pi: %.2f\n", 3.14159; # 3.14
printf "Hex: %x\n", 255; # ff
printf "Oct: %o\n", 8; # 10
printf "Bin: %b\n", 5; # 101
printf "Sci: %e\n", 123456; # 1.234560e+05
# Width and alignment
printf "%10s|\n", "hi"; # ' hi|' (right)
printf "%-10s|\n", "hi"; # 'hi |' (left)
printf "%05d\n", 42; # '00042' (zero-pad)
printf "%+d\n", 42; # '+42' (force sign)sprintf
sprintf is identical to printf but returns the formatted string instead of printing it. Useful for building strings, log lines, and tabular output. Use %% for a literal percent sign.
# sprintf returns the formatted string
my $date = sprintf("%04d-%02d-%02d", 2024, 1, 15);
# "2024-01-15"
my $log = sprintf("[%s] %s: %s",
scalar localtime, "INFO", "message");
# String padding
my $right = sprintf("%20s", "hi");
my $left = sprintf("%-20s|", "hi");
# Multiple formatted values
my $row = sprintf("%-10s %5d %8.2f",
"apple", 3, 1.50);
# Literal percent
my $pct = sprintf("%d%%", 50); # "50%"format & write
Perl's report format feature defines columnar templates with field holders. write() renders the current values. It's old-fashioned but handy for fixed-width reports. Modern code often uses printf/sprintf instead.
# Define a report format for STDOUT
format STDOUT =
@<<<<<<<<<<<<<< @>>>>>>
$name, $amount
.
my $name = "Alice";
my $amount = 1234.56;
write; # outputs the formatted line: Alice 1234.56
# Optional top-of-page (header) format
format STDOUT_TOP =
Name Amount
----------------------
.
# $~ sets the format name, $^ sets the top-of-page formatField Holders
Field holders define alignment and width in formats: @< left, @> right, @| center, @# numeric. ~ suppresses blank lines; ~~ repeats until a field is exhausted; ^ enables word-wrapping for multi-line text.
# Format field holders:
# @<<< left-justified (min width = number of chars)
# @>>> right-justified
# @||| centered
# @###.## numeric (right-justified, with decimals)
# @* multiline (takes whole lines)
format REPORT =
@<<<<<<<<<<<<<<<< @>>>>.##
$text, $num
.
# ~ suppresses a blank line if fields are empty
# ~~ repeats the line until a field is exhausted
format BLOCK =
~~ ^<<<<<<<<<<<<<<<<<
$text
.
# ^ enables word-wrap into the fieldFormat Specifiers
Specifiers map values to text: %s strings, %d/%u integers, %f floats, %e/%E scientific, %x/%o/%b number bases. Flags adjust formatting; width and precision (N.M) control field size and decimal places.
# printf / sprintf specifiers
%% literal percent
%s string
%c character (from code point)
%d signed integer
%u unsigned integer
%f floating point
%e scientific (lowercase)
%E scientific (uppercase)
%x hex (lowercase) %X hex (uppercase)
%o octal
%b binary
# Flags: - + 0 space #
printf "%-10s|%+d|%05d| %#x", "x", 5, 5, 255;
# Width and precision: %N.M
printf "%.3f", 3.14159; # 3.142
printf "%8.2f", 3.14159; # ' 3.14'Padding & Alignment
Use %-Ns to left-align, %Ns to right-align, %0Nd to zero-pad numbers. There's no built-in center specifier; compute padding manually or post-process with tr/// to replace spaces with another fill character.
# Right-align (default for numbers)
my $r = sprintf("%10s", "hi"); # ' hi'
# Left-align with '-'
my $l = sprintf("%-10s|", "hi"); # 'hi |'
# Zero-pad numbers
my $z = sprintf("%05d", 42); # '00042'
# Center manually
sub center {
my ($s, $w) = @_;
my $pad = int(($w - length($s)) / 2);
return ' ' x $pad . $s . ' ' x ($w - $pad - length($s));
}
print center("Title", 20);
# Replace spaces with another char
my $dashed = sprintf("%20s", "x");
$dashed =~ tr/ /-/; # '-------------------x'Related Perl snippets
Copy-paste ready code for common tasks.
Scalars, Arrays, and Hashes
Perl's three main data types with sigils.
Regular Expressions
Perl is famous for its powerful regex support.
Subroutines and References
Define subs and use references for complex data.
Complex Data Structures
Build nested structures with references.
Modules and Packages
Create reusable modules with package keyword.
File I/O
Read and write files with filehandles.
OOP with Moose
Modern object-oriented programming in Perl.
One-Liners and CLI Tricks
Common Perl one-liners for text processing.
Was this helpful?