Syntax Basics
Hello, World
puts "Hello, World!" use v5.38;
say "Hello, World!"; use v5.38 is the modern Perl preamble β it enables strict, warnings, and the say function all at once. say is Ruby's puts: it prints the string followed by a newline. Without say, the equivalent is print "Hello, World!\n" with an explicit newline. Every Perl statement ends with a semicolon.Comments
# Single-line comment
=begin
Multi-line comment
(rarely used)
=end
name = "Alice" # inline
puts name use v5.38;
# Single-line comment β the only inline comment syntax
# Perl has no multi-line comment syntax.
# Block documentation uses Pod: =begin comment ... =end comment
# In practice, Perl programmers just prefix every line with #.
my $name = "Alice"; # inline comment
say $name; Perl only has single-line comments with
# β exactly like Ruby. There is no /* */ block comment syntax. Multi-line documentation uses Pod (Plain Old Documentation): =begin comment β¦ =end comment, but this is rare in everyday code. Ruby's =begin / =end block comment was borrowed directly from Perl's Pod syntax.Semicolons required
greeting = "Hello"
name = "World"
puts "#{greeting}, #{name}!" use v5.38;
my $greeting = "Hello";
my $name = "World";
say "$greeting, $name!"; Every Perl statement must end with a semicolon β there is no optional-semicolon rule. Forgetting a semicolon is the most common Perl beginner error. Ruby's newline-as-statement-terminator does not apply in Perl. The
my keyword declares a lexically scoped variable; use v5.38 (which enables strict) makes my mandatory.print vs say
print "no newline"
print "
"
puts "with newline" use v5.38;
print "no newline";
print "
";
say "with newline"; # say appends a newline automatically
say 42; # works with numbers too
say ""; # prints a blank line say (available since Perl 5.10, enabled by use v5.38) works exactly like Ruby's puts β it appends a newline automatically. print is like Ruby's print: no trailing newline. say is almost always preferred for interactive output.Truthiness
# false: false, nil
# true: everything else (including 0, "", [])
puts "0 is #{0 ? 'truthy' : 'falsy'}"
puts "empty string is #{"" ? 'truthy' : 'falsy'}"
puts "nil is #{nil ? 'truthy' : 'falsy'}"
puts "[] is #{[] ? 'truthy' : 'falsy'}" use v5.38;
# false: undef, 0, "", "0"
# true: everything else
for my $value (undef, 0, "", "0") {
my $label = defined($value) ? "'$value'" : "undef";
say "$label is " . ($value ? "truthy" : "falsy");
}
say "---";
for my $value (1, "a", "00", " ", 0.1) {
say "'$value' is " . ($value ? "truthy" : "falsy");
} Perl's false values are
undef, 0, "" (empty string), and "0" (the string zero). Everything else is true β including "00", " " (a space), and even "false". Ruby is simpler: only false and nil are falsy. The "0" trap catches many Rubyists off guard.Variables & Sigils
Scalar variables ($)
name = "Alice"
age = 30
pi = 3.14159
nothing = nil
puts "#{name} is #{age} years old"
puts "Pi is approximately #{pi}" use v5.38;
my $name = "Alice";
my $age = 30;
my $pi = 3.14159;
my $nothing = undef;
say "$name is $age years old";
say "Pi is approximately $pi";
say defined($nothing) ? "defined" : "undef"; All scalar variables begin with
$. The my keyword declares a lexically scoped variable β always required when use strict is active (which use v5.38 enables). Ruby uses bare names for local variables; Perl requires the sigil always. Strings and numbers are the same scalar type β Perl coerces between them automatically.Array variables (@)
colors = ["red", "green", "blue"]
numbers = (1..5).to_a
puts colors.join(" ")
puts colors[0]
puts colors[-1]
puts colors.length use v5.38;
my @colors = ("red", "green", "blue");
my @numbers = (1..5);
say "@colors"; # interpolates with spaces
say $colors[0]; # first element β note the $ sigil
say $colors[-1]; # last element
say scalar(@colors); # count Array variables use the
@ sigil. Interpolating @colors in a double-quoted string inserts all elements separated by a space. Individual elements use $ (not @) because you are accessing a scalar value from the array. The scalar() function forces scalar context, returning the element count β Perl's equivalent of Ruby's .length.Hash variables (%)
person = {
name: "Alice",
age: 30,
city: "Portland"
}
puts person[:name]
puts person[:age] use v5.38;
my %person = (
name => "Alice",
age => 30,
city => "Portland",
);
say $person{name};
say $person{age}; Hash variables use the
% sigil. The fat comma => auto-quotes the left side, so barewords like name do not need quotes. Individual values use $ because you are accessing a scalar. Hash elements use curly braces {} while array elements use square brackets [].Context sensitivity
animals = ["cat", "dog", "bird"]
count = animals.length # explicit method call
puts count
puts animals.join(" ") use v5.38;
my @animals = ("cat", "dog", "bird");
my $count = @animals; # scalar context: returns count
say "Count: $count";
my $also = scalar @animals; # explicit scalar context
say "Also: $also";
# String concatenation forces scalar context too
say "Total: " . @animals; Perl's most distinctive feature is context: the same expression evaluates differently depending on whether a scalar or list is expected. In scalar context, an array returns its element count. In list context, it returns its elements. Forgetting this causes subtle bugs β for example,
my $count = @array gives the count, not a reference. Ruby requires an explicit .length or .size call; Perl infers it from context.Sigil changes on access
letters = ["a", "b", "c", "d"]
puts letters[1] # single element
puts letters[0, 2].join # slice use v5.38;
my @letters = ("a", "b", "c", "d");
my %scores = (alice => 95, bob => 87);
say $letters[1]; # $ for single element
my @slice = @letters[0, 2]; # @ for array slice
say "@slice";
say $scores{alice}; # $ for single hash value
my @pair = @scores{qw(alice bob)}; # @ for hash slice
say "@pair"; The sigil changes based on what you are retrieving, not where it lives. A single element always uses
$. A slice (multiple elements at once) uses @. This "sigil follows context" rule is one of Perl's most confusing aspects for newcomers, but it is perfectly consistent: $ means "one thing", @ means "multiple things".undef (Perl's nil)
missing = nil
puts missing.nil?
name = "Alice"
name = nil
puts name.nil?
puts missing || "default" use v5.38;
my $missing; # undef by default
say defined($missing) ? "defined" : "undef";
my $name = "Alice";
$name = undef;
say defined($name) ? "defined" : "undef";
# Defined-or operator: // tests definedness, not truthiness
say $missing // "default";
say 0 // "default"; # 0 is defined, so: 0 undef is Perl's equivalent of Ruby's nil. Use defined() to test for it β a plain boolean test is insufficient because 0 and "" are also false but are defined. The // defined-or operator (Perl 5.10+) tests definedness rather than truthiness, making it safer than || when 0 or "" are valid values.Strings
Concatenation and repetition
first = "Hello"
last = "World"
full = first + ", " + last + "!"
puts full
line = "-" * 20
puts line use v5.38;
my $first = "Hello";
my $last = "World";
my $full = $first . ", " . $last . "!";
say $full;
my $line = "-" x 20;
say $line; Perl uses
. for string concatenation (not + as in JavaScript). The x operator repeats a string a given number of times β Ruby uses * for the same purpose. Using + for string concatenation in Perl silently coerces both sides to numbers, which is almost always a bug.Double-quote interpolation
name = "Alice"
numbers = [1, 2, 3]
colors = {sky: "blue"}
puts "Hello, #{name}!"
puts "Numbers: #{numbers.join(' ')}"
puts "Sky is #{colors[:sky]}" use v5.38;
my $name = "Alice";
my @numbers = (1, 2, 3);
my %colors = (sky => "blue");
say "Hello, $name!";
say "Numbers: @numbers"; # arrays expand with spaces
say "Sky is $colors{sky}";
say 'Single-quoted: no $name interpolation'; Double-quoted strings interpolate
$scalar, @array (space-separated), and $hash{key} directly. Single-quoted strings interpolate nothing β only \\ and \' are special. Perl's interpolation is simpler than Ruby's #{} β you cannot embed arbitrary expressions directly, but scalar/array/hash access covers the common cases.Heredocs
name = "World"
message = <<~TEXT
Hello, #{name}!
This is a heredoc.
TEXT
puts message use v5.38;
my $name = "World";
my $message = <<END;
Hello, $name!
This is a heredoc.
It interpolates variables.
END
say $message;
my $literal = <<'NOINTERP';
No $interpolation here.
NOINTERP
print $literal; Perl heredocs use
<<LABEL. Double-quoted heredocs interpolate variables; single-quoted heredocs (<<'LABEL') do not β matching the behavior of regular quoted strings. Perl 5.26+ added <<~LABEL (with tilde) which strips leading whitespace from each line, identical to Ruby's <<~HEREDOC.chomp β strip trailing newline
line = "Hello, World!
"
line.chomp!
puts "[#{line}]"
lines = ["foo
", "bar
", "baz
"]
lines.each(&:chomp!)
puts lines.join(", ") use v5.38;
my $line = "Hello, World!
";
chomp $line; # removes trailing newline in place
say "[$line]";
my @lines = ("foo
", "bar
", "baz
");
chomp @lines; # chomps every element
say join(", ", @lines); chomp removes the trailing record separator (usually \n) from a string or all elements of an array, modifying in place. It is idiomatic in any Perl program that reads input line by line β while (<$fh>) { chomp; ... }. Ruby's String#chomp! is a direct descendant. Unlike Ruby's strip, chomp only removes the line separator, not all whitespace.String functions
text = "Hello, World!"
puts text.length
puts text.upcase
puts text.downcase
puts "hello".capitalize
puts text[0, 5]
puts text.index("World") use v5.38;
my $text = "Hello, World!";
say length($text); # 13
say uc($text); # HELLO, WORLD!
say lc($text); # hello, world!
say ucfirst("hello world"); # Hello world
say substr($text, 0, 5); # Hello
say index($text, "World"); # 7 Perl's string functions are standalone functions, not methods β you pass the string as an argument:
uc($string) rather than $string.upcase. The direct equivalents are: length β String#length, uc β #upcase, lc β #downcase, ucfirst β #capitalize, substr β #slice, index β #index.Arrays
Creating arrays
numbers = (1..10).to_a
letters = ('a'..'f').to_a
words = %w[apple banana cherry]
puts numbers.join(" ")
puts letters.join(" ")
puts words.first use v5.38;
my @numbers = (1..10);
my @letters = ('a'..'f');
my @words = qw(apple banana cherry); # qw = quote words
say "@numbers";
say "@letters";
say $words[0]; The
.. range operator creates a list of consecutive numbers or single characters, identical to Ruby's ... The qw(...) (quote-words) operator creates a list of whitespace-separated strings without requiring quotes around each word β Ruby's equivalent is the %w[] literal. Arrays in Perl are dynamic and grow automatically, just like Ruby arrays.push, pop, shift, unshift
items = ["b", "c"]
items.push("d", "e")
items.unshift("a")
puts items.join(" ")
last = items.pop
first = items.shift
puts "Got: #{first}, #{last}"
puts items.join(" ") use v5.38;
my @items = ("b", "c");
push @items, "d", "e"; # append one or more to end
unshift @items, "a"; # prepend to front
say "@items";
my $last = pop @items; # remove from end
my $first = shift @items; # remove from front
say "Got: $first, $last";
say "@items"; push/pop work on the tail; shift/unshift work on the head β identical in purpose to Ruby's Array#push, #pop, #shift, #unshift. In Perl these are standalone functions that take the array as the first argument rather than methods called on the array.grep and map
numbers = (1..10).to_a
evens = numbers.select { |n| n % 2 == 0 }
doubled = numbers.map { |n| n * 2 }
puts evens.join(" ")
puts doubled.join(" ") use v5.38;
my @numbers = (1..10);
my @evens = grep { $_ % 2 == 0 } @numbers;
my @doubled = map { $_ * 2 } @numbers;
say "@evens";
say "@doubled";
my @words = qw(apple banana cherry date elderberry);
my @long = grep { length($_) > 5 } @words;
say "@long"; grep is Perl's equivalent of Ruby's select β it filters an array, keeping elements where the block is true. map transforms each element, identical to Ruby's map. Both use $_ as the current element in the block. They are fundamental Perl idioms that predate Ruby's equivalent methods β Ruby borrowed the concept from Perl.sort and reverse
words = %w[banana apple cherry date]
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
puts words.sort.join(", ")
puts numbers.sort.join(", ")
puts numbers.sort { |a, b| b <=> a }.join(", ")
puts words.reverse.join(", ") use v5.38;
my @words = qw(banana apple cherry date);
my @numbers = (3, 1, 4, 1, 5, 9, 2, 6);
say join(", ", sort @words);
say join(", ", sort { $a <=> $b } @numbers); # numeric ascending
say join(", ", sort { $b <=> $a } @numbers); # numeric descending
say join(", ", reverse @words); sort without a comparison block sorts lexicographically. For numeric sort, provide a block using the special variables $a and $b. The <=> spaceship operator returns -1, 0, or 1 for numeric comparisons; cmp does the same for strings. Ruby's <=> is directly inherited from Perl, which invented the operator.join and split
words = %w[one two three four]
csv = words.join(", ")
puts csv
path = "usr/local/bin"
parts = path.split("/")
puts parts.length
puts parts.last use v5.38;
my @words = qw(one two three four);
my $csv = join(", ", @words);
say $csv;
my $path = "usr/local/bin";
my @parts = split("/", $path);
say scalar(@parts);
say $parts[-1];
my @tokens = split(" ", "hello world foo");
say "@tokens"; join takes the separator first, then the array β unlike Ruby's Array#join which takes the separator as a method argument. split takes a regex or string delimiter and the target string. Both work identically to Ruby's equivalents. Note the argument order: in Perl join(separator, @array); in Ruby array.join(separator).splice β insert and remove
letters = ('a'..'f').to_a
removed = letters.slice!(1, 2)
puts removed.join(" ")
puts letters.join(" ") use v5.38;
my @letters = ('a'..'f');
# splice(array, offset, length) β remove elements
my @removed = splice(@letters, 1, 2);
say "Removed: @removed";
say "Remaining: @letters";
my @items = qw(a b e f);
splice(@items, 2, 0, "c", "d"); # insert without removing
say "@items"; splice is Perl's all-in-one array modifier. It can remove elements (like Ruby's slice! or delete_at), insert elements (like insert), or replace elements β all in one call. The arguments are the array, the starting offset, the number of elements to remove, and optionally replacement elements. The removed elements are returned.Hashes
Creating hashes
capitals = {
france: "Paris",
germany: "Berlin",
japan: "Tokyo"
}
puts capitals[:france]
puts capitals[:japan] use v5.38;
my %capitals = (
france => "Paris",
germany => "Berlin",
japan => "Tokyo",
);
say $capitals{france};
say $capitals{japan}; Hashes use the
% sigil and curly braces for element access. The fat comma => auto-quotes the left operand, so barewords are fine as keys. Hash values are accessed with $hash{key} β the $ sigil because you are retrieving a single scalar value. Ruby's hash[:key] and Perl's $hash{key} serve the same purpose.keys, values, each
scores = {alice: 95, bob: 87, carol: 92}
puts scores.keys.sort.join(", ")
puts scores.values.sort.join(", ")
scores.each { |name, score| puts "#{name}: #{score}" } use v5.38;
my %scores = (alice => 95, bob => 87, carol => 92);
say join(", ", sort keys %scores);
say join(", ", sort { $a <=> $b } values %scores);
while (my ($name, $score) = each %scores) {
say "$name: $score";
} keys and values return lists in arbitrary order β always sort when order matters. each iterates key-value pairs two at a time; it is Perl's equivalent of Ruby's Hash#each. The hash iteration order is randomized per-process in modern Perl (a security feature against hash-flooding attacks), mirroring Ruby's insertion-order guarantee only from Ruby 1.9 onward.exists and delete
settings = {color: "blue", size: "large"}
puts settings.key?(:color)
puts settings.key?(:weight)
settings.delete(:color)
puts settings.keys.sort.join(", ") use v5.38;
my %settings = (color => "blue", size => "large");
if (exists $settings{color}) {
say "color: $settings{color}";
}
say exists $settings{weight} ? "has weight" : "no weight";
delete $settings{color};
say join(", ", sort keys %settings); exists tests whether a key is present β not whether the value is defined. A key can exist with an undef value. delete removes a key-value pair entirely and returns the value. Ruby's equivalents are Hash#key? (or has_key?) and Hash#delete. Both languages use the same method names for the same operations.Hash slices
person = {name: "Alice", age: 30, city: "Portland", job: "dev"}
fields = person.values_at(:name, :age, :city)
puts fields.join(", ") use v5.38;
my %person = (name => "Alice", age => 30, city => "Portland");
# Extract multiple values at once
my @fields = @person{qw(name age city)};
say join(", ", @fields);
# Assign multiple keys at once
@person{qw(country language)} = ("USA", "Ruby");
say "$person{name} speaks $person{language}"; A hash slice uses
@hash{@keys} to access or assign multiple keys at once. The @ sigil signals a list result. qw(...) is the word-list quoting shorthand. You can also assign a slice: @hash{@keys} = @values. Ruby achieves the same with Hash#values_at for reading.Control Flow
if / elsif / else
score = 85
if score >= 90
puts "A"
elsif score >= 80
puts "B"
elsif score >= 70
puts "C"
else
puts "Below C"
end use v5.38;
my $score = 85;
if ($score >= 90) {
say "A";
} elsif ($score >= 80) {
say "B";
} elsif ($score >= 70) {
say "C";
} else {
say "Below C";
} Perl uses
elsif (no extra 'e') β a common stumbling block coming from other languages. The condition parentheses are required in Perl; Ruby makes them optional. Curly braces are always required around the body in Perl. This is the same structure Ruby uses, just with mandatory parentheses and braces.unless and ternary
logged_in = false
unless logged_in
puts "Please log in"
end
result = logged_in ? "Welcome!" : "Please log in"
puts result use v5.38;
my $logged_in = 0;
unless ($logged_in) {
say "Please log in";
}
my $greeting = $logged_in ? "Welcome back!" : "Please log in";
say $greeting; Perl has
unless β Ruby borrowed it directly. Both languages use the same keyword for the same purpose: run the block when the condition is false. The ternary operator ? : is also identical. Perl additionally has until (negate while) and postfix forms for both, all of which Ruby also adopted.Postfix conditionals
temperature = 95
puts "It is hot!" if temperature > 90
puts "It is cold!" unless temperature < 32
%w[wash dry fold].each { |task| puts "task: #{task}" } use v5.38;
my $temperature = 95;
my @tasks = qw(wash dry fold);
say "It is hot!" if $temperature > 90;
say "It is cold!" unless $temperature < 32;
say "task: $_" for @tasks; Postfix
if, unless, while, until, and for/foreach are all valid in Perl. Ruby borrowed all of these directly. Postfix forms make short conditional statements read like English and are idiomatic in both languages. In Perl's postfix for, the current element is available in $_.foreach and C-style for
%w[apple banana cherry].each do |fruit|
puts fruit
end
(1..5).each { |n| puts n } use v5.38;
my @fruits = qw(apple banana cherry);
foreach my $fruit (@fruits) {
say $fruit;
}
for my $number (1..5) { # for is an alias for foreach
say $number;
}
for (my $i = 0; $i < 3; $i++) { # C-style for
say "index $i";
} for and foreach are interchangeable aliases in Perl. The idiomatic form declares the loop variable with my. The C-style three-expression for loop also exists. Ruby's block-based each is the direct equivalent of foreach β the iteration mechanism differs (method + block vs. statement), but the purpose is identical.while and until
count = 0
while count < 3
puts "count: #{count}"
count += 1
end
remaining = 3
until remaining == 0
puts "remaining: #{remaining}"
remaining -= 1
end use v5.38;
my $count = 0;
while ($count < 3) {
say "count: $count";
$count++;
}
my $remaining = 3;
until ($remaining == 0) {
say "remaining: $remaining";
$remaining--;
} until is the negated while β Perl and Ruby share it. Perl also has do { ... } while (...) which runs the body at least once. Perl's ++ and -- operators are inherited from C; Ruby also has them. The string increment $str++ is a unique Perl feature: "aa"++ yields "ab".last and next
(1..10).each do |n|
next if n.even?
break if n > 7
puts n
end use v5.38;
for my $number (1..10) {
next if $number % 2 == 0; # skip even (like Ruby's next)
last if $number > 7; # stop loop (like Ruby's break)
say $number;
} last is Perl's break β equivalent to Ruby's break. next skips to the next iteration β equivalent to Ruby's next. Perl also has redo, which restarts the current iteration without re-evaluating the condition. Ruby's next keyword was borrowed directly from Perl.Subroutines
Defining subroutines
def greet(name)
"Hello, #{name}!"
end
puts greet("Alice")
puts greet("Bob") use v5.38;
sub greet {
my ($name) = @_;
return "Hello, $name!";
}
say greet("Alice");
say greet("Bob"); Subroutines are declared with
sub. All arguments arrive as a flat list in the special array @_. The idiomatic first line of any subroutine is my ($arg1, $arg2) = @_ to unpack arguments into named variables. The return keyword is optional β Perl returns the last evaluated expression, just like Ruby.Default parameters
def make_greeting(name, prefix: "Hello")
"#{prefix}, #{name}!"
end
puts make_greeting("Alice")
puts make_greeting("Bob", prefix: "Hi") use v5.38;
sub make_greeting {
my ($name, $prefix) = @_;
$prefix //= "Hello"; # defined-or: use default if undef
return "$prefix, $name!";
}
say make_greeting("Alice");
say make_greeting("Bob", "Hi");
say make_greeting("Carol", "Greetings"); Perl has no built-in default parameter syntax. The idiomatic pattern is
$var //= "default" β the defined-or assignment. This is safer than ||= because ||= treats 0 and "" as missing, while //= only treats undef as missing. Ruby 2.0+ added native keyword arguments with defaults; Perl relies on this convention.Multiple return values
def min_max(*numbers)
[numbers.min, numbers.max]
end
minimum, maximum = min_max(3, 1, 4, 1, 5, 9, 2, 6)
puts "Min: #{minimum}, Max: #{maximum}" use v5.38;
sub min_max {
my @numbers = @_;
my @sorted = sort { $a <=> $b } @numbers;
return ($sorted[0], $sorted[-1]);
}
my ($minimum, $maximum) = min_max(3, 1, 4, 1, 5, 9, 2, 6);
say "Min: $minimum, Max: $maximum"; Perl subroutines return a flat list β just use a list return and unpack with parallel assignment. There is no need to wrap results in an array. Ruby also supports multiple return values via array unpacking (
return [a, b] and a, b = method), but Perl's mechanism is built into the language's core list semantics.Anonymous subroutines
double = ->(x) { x * 2 }
add = ->(x, y) { x + y }
puts double.call(5)
puts add.call(3, 4)
result = [1, 2, 3, 4, 5].map(&double)
puts result.join(" ") use v5.38;
my $double = sub { $_[0] * 2 };
my $add = sub { $_[0] + $_[1] };
say $double->(5);
say $add->(3, 4);
sub apply {
my ($func, @values) = @_;
return map { $func->($_) } @values;
}
my @results = apply($double, 1, 2, 3, 4, 5);
say "@results"; Anonymous subroutines are created with
sub { ... } without a name and stored in a scalar. They are invoked using the arrow operator ->(). Perl closures capture lexical variables just as Ruby's blocks and lambdas do. This is Perl's equivalent of Ruby's -> lambda or Proc.new.wantarray β context-sensitive return
# Ruby has no equivalent β methods return what they return
def flexible
[1, 2, 3] # always returns an array
end
list = flexible
value = flexible.first
puts list.inspect
puts value use v5.38;
sub flexible {
if (wantarray) {
return (1, 2, 3); # list context
} else {
return "scalar"; # scalar context
}
}
my @list = flexible(); # list context
my $value = flexible(); # scalar context
say "@list";
say $value; wantarray lets a subroutine detect whether its caller expects a list or a scalar, and return different things accordingly. This is a uniquely Perl capability β Ruby has no equivalent. Many built-in Perl functions are context-sensitive in this way. It is powerful but can make code surprising to read; modern Perl style prefers explicit return values.Regular Expressions
Match operator =~
email = "user@example.com"
if email.match?(/@.*./)
puts "Looks like an email"
end
text = "The quick brown fox"
if (match = text.match(/(w+)s+(w+)$/))
puts "Last two words: #{match[1]} #{match[2]}"
end use v5.38;
my $email = 'user@example.com';
if ($email =~ /@.*./) {
say "Looks like an email";
}
my $text = "The quick brown fox";
if ($text =~ /(w+)s+(w+)$/) {
say "Last two words: $1 $2";
} The
=~ binding operator applies a regex to a string β Ruby inherited this directly from Perl, including the operator name. Parentheses in the pattern capture into $1, $2, etc. β also identical to Ruby's $1, $2. Perl's regex engine is the canonical one that most modern languages reference.Substitution s///
text = "The cat sat on the mat"
changed = text.sub("cat", "dog")
puts changed
message = "hello world hello"
replaced = message.gsub("hello", "hi")
puts replaced use v5.38;
my $text = "The cat sat on the mat";
(my $changed = $text) =~ s/cat/dog/;
say $changed;
my $message = "hello world hello";
(my $all = $message) =~ s/hello/hi/g; # /g = all occurrences
say $all; s/pattern/replacement/flags substitutes the first match. Adding /g replaces all occurrences β like Ruby's gsub. Without /g, only the first match is replaced β like Ruby's sub. Perl's s/// modifies the string in place; Ruby's sub/gsub return new strings (use sub!/gsub! for in-place). The (my $copy = $orig) =~ s/... pattern creates a modified copy.Named captures
date = "2026-04-22"
if (match = date.match(/(?<year>d{4})-(?<month>d{2})-(?<day>d{2})/))
puts "Year: #{match[:year]}"
puts "Month: #{match[:month]}"
puts "Day: #{match[:day]}"
end use v5.38;
my $date = "2026-04-22";
if ($date =~ /(?<year>d{4})-(?<month>d{2})-(?<day>d{2})/) {
say "Year: $+{year}";
say "Month: $+{month}";
say "Day: $+{day}";
} Named captures use
(?<name>...) and are accessed via the %+ hash. Ruby uses the identical (?<name>...) syntax and accesses them via MatchData with match[:name]. Perl introduced named captures in version 5.10; Ruby adopted exactly the same syntax.Global match β collect all
text = "The price is $10 and $25 and $100"
prices = text.scan(/$(d+)/).map(&:first)
puts prices.join(", ") use v5.38;
my $text = 'The price is $10 and $25 and $100';
my @prices = ($text =~ /$(d+)/g);
say join(", ", @prices);
my $html = "<b>bold</b> and <i>italic</i>";
while ($html =~ /<(w+)>/g) {
say "Tag: $1";
} In list context,
=~ with /g collects all matches at once β identical to Ruby's String#scan. In scalar context inside a while loop, /g iterates through matches one at a time using an internal position pointer (pos()). Both patterns are idiomatic Perl for processing all occurrences of a pattern in a string.Regex modifiers
text = "Hello World"
puts text.match?(/hello/i)
puts text.match?(/hello world/ix) use v5.38;
my $text = "Hello World";
say "case-insensitive" if $text =~ /hello/i;
# /x allows whitespace and comments in the pattern
my $date = "2026-04-22";
if ($date =~ /
(d{4}) # year
-
(d{2}) # month
-
(d{2}) # day
/x) {
say "Year: $1, Month: $2, Day: $3";
} Perl regex modifiers follow the closing delimiter:
/i for case-insensitive, /g for global, /x for extended (whitespace and comments allowed), /s for dot-matches-newline, /m for multiline anchors. Ruby uses the same modifiers in the same position β all inherited from Perl. The /x modifier is especially valuable for complex patterns.References
References to scalars
# Ruby objects are always references β no explicit reference syntax
name = "Alice"
ref = name # both point to same object
puts ref use v5.38;
my $name = "Alice";
my $ref = \$name; # backslash creates a reference
say $$ref; # $$ dereferences
say ref($ref); # "SCALAR" β what type of reference
$$ref = "Bob"; # modify via reference
say $name; # "Bob" β original is changed A reference is a scalar holding the memory address of another value. References are created with
\ and dereferenced with $$ (scalar), @$arr (array), or %$hash (hash). The ref() function returns the type string ("SCALAR", "ARRAY", "HASH", "CODE"). Ruby does not need explicit references because all objects are already reference types.Anonymous references
arr = [1, 2, 3]
hash = {name: "Alice", age: 30}
puts arr[1]
puts hash[:name]
arr.push(4)
puts arr.join(" ") use v5.38;
my $arr = [1, 2, 3]; # anonymous array reference
my $hash = {name => "Alice", age => 30}; # anonymous hash ref
say $arr->[1];
say $hash->{name};
push @$arr, 4;
say "@$arr"; [...] creates an anonymous array reference and {...} creates an anonymous hash reference β these are directly analogous to Ruby's [] and {} which create Array and Hash objects. The arrow operator -> dereferences and accesses an element in one step: $arr->[0] and $hash->{key}.Nested data structures
people = [
{name: "Alice", scores: [95, 87, 92]},
{name: "Bob", scores: [78, 83, 90]},
]
people.each do |person|
avg = person[:scores].sum.to_f / person[:scores].size
puts "#{person[:name]}: #{avg.round(1)}"
end use v5.38;
my @people = (
{ name => "Alice", scores => [95, 87, 92] },
{ name => "Bob", scores => [78, 83, 90] },
);
for my $person (@people) {
my @scores = @{ $person->{scores} };
my $total = 0;
$total += $_ for @scores;
my $average = $total / scalar @scores;
say "$person->{name}: $average";
} Complex data structures in Perl are built from references. An array of hash references is the direct equivalent of Ruby's array of hashes. The
-> arrow chains dereferencing: $person->{scores} gets the array reference, then @{...} dereferences it to a list. Perl requires explicit reference/dereference steps where Ruby's uniform object model makes this invisible.Code references (callbacks)
operations = {
double: ->(x) { x * 2 },
square: ->(x) { x ** 2 },
}
puts operations[:double].call(5)
puts operations[:square].call(4) use v5.38;
my %operations = (
double => sub { $_[0] * 2 },
square => sub { $_[0] ** 2 },
);
say $operations{double}->(5);
say $operations{square}->(4);
# A function that takes a callback
sub transform {
my ($func, @items) = @_;
return map { $func->($_) } @items;
}
my @results = transform($operations{double}, 1..5);
say "@results"; Code references (anonymous subroutines stored in scalars or hashes) are Perl's first-class functions. They can be stored in data structures, passed as arguments, and returned from subroutines β exactly like Ruby's
Proc and lambda objects. The ref($func) eq "CODE" check confirms a variable holds a code reference.OOP with bless
Package and bless
class Animal
def initialize(name:, sound:)
@name = name
@sound = sound
end
def speak
puts "#{@name} says #{@sound}"
end
end
dog = Animal.new(name: "Rex", sound: "Woof")
dog.speak use v5.38;
package Animal;
sub new {
my ($class, %args) = @_;
return bless {
name => $args{name},
sound => $args{sound} // "...",
}, $class;
}
sub speak {
my $self = shift;
say "$self->{name} says $self->{sound}";
}
package main;
my $dog = Animal->new(name => "Rex", sound => "Woof");
$dog->speak; Perl OOP is built on
bless β a function that associates a hash reference with a package name, making it an object. The new constructor is a convention, not a keyword. my $self = shift is the idiomatic way to extract the invocant (the object the method was called on). Ruby's class, initialize, and self make OOP more ergonomic; Perl's approach is explicit and manual.Accessors (getters and setters)
class Person
attr_reader :name
attr_accessor :age
def initialize(name, age)
@name = name
@age = age
end
end
person = Person.new("Alice", 30)
puts person.name
person.age = 31
puts person.age use v5.38;
package Person;
sub new {
my ($class, %args) = @_;
return bless { name => $args{name}, age => $args{age} }, $class;
}
sub name { $_[0]->{name} } # read-only
sub age { # read-write
my $self = shift;
$self->{age} = shift if @_;
return $self->{age};
}
package main;
my $person = Person->new(name => "Alice", age => 30);
say $person->name;
$person->age(31);
say $person->age; Perl has no built-in accessor generation β every getter and setter is written by hand. The combined getter/setter pattern (
if @_ to detect a setter call) is a common Perl idiom. Ruby's attr_reader, attr_writer, and attr_accessor generate these methods automatically; the CPAN modules Moose and Moo provide similar convenience for Perl.Inheritance
class Animal
def initialize(name) = @name = name
def name = @name
def speak = puts "#{@name} makes a sound"
end
class Dog < Animal
def speak = puts "#{name} barks"
end
dog = Dog.new("Rex")
dog.speak
puts dog.is_a?(Animal) use v5.38;
package Animal;
sub new { bless { name => $_[1] }, $_[0] }
sub name { $_[0]->{name} }
sub speak { say $_[0]->{name} . " makes a sound" }
package Dog;
our @ISA = ('Animal'); # declare inheritance
sub speak {
my $self = shift;
say $self->name . " barks";
}
package main;
my $dog = Dog->new("Rex");
$dog->speak;
say $dog->isa("Animal") ? "is an Animal" : "not an Animal"; Perl inheritance is declared by setting
@ISA to a list of parent class names. Method resolution searches @ISA depth-first. SUPER:: calls a parent method. This manual mechanism does what Ruby's class Dog < Animal does automatically. Modern Perl typically uses use parent 'Animal' instead of manipulating @ISA directly.Error Handling
die and eval
begin
raise "Something went wrong"
rescue => error
puts "Caught: #{error.message}"
end use v5.38;
eval {
die "Something went wrong
";
say "This won't print";
};
if ($@) {
say "Caught: $@";
}
# $@ is cleared on the next successful eval
eval { 1 };
say defined($@) && $@ eq "" ? "error cleared" : "error: $@"; die is Perl's raise; eval { } is the try-block equivalent of Ruby's begin/rescue/end. After an eval, $@ holds the caught exception (or "" on success). If die is given a string ending in \n, Perl omits the at file line N suffix β a useful convention for user-facing error messages.warn β non-fatal warnings
$stderr.puts "Something looks odd" # to stderr
warn "Something looks odd" # same
puts "Execution continues" use v5.38;
warn "Something looks suspicious
"; # prints to STDERR
say "Execution continues";
my $value = -1;
warn "Negative value: $value
" if $value < 0;
say "Processing: $value"; warn prints to STDERR and continues execution β identical to Ruby's warn. Without a trailing \n, Perl appends the file and line number to the message, which is useful for debugging. The $SIG{__WARN__} handler can intercept warnings, similar to Ruby's Warning module.or die β inline error checking
def divide(numerator, denominator)
raise "Cannot divide by zero" if denominator == 0
numerator.to_f / denominator
end
begin
puts divide(10, 2)
puts divide(10, 0)
rescue => error
puts "Error: #{error.message}"
end use v5.38;
sub divide {
my ($numerator, $denominator) = @_;
die "Cannot divide by zero
" if $denominator == 0;
return $numerator / $denominator;
}
my $result = eval { divide(10, 2) };
say "10 / 2 = $result" unless $@;
eval { divide(10, 0) };
say "Error: $@" if $@; The
or die pattern is ubiquitous in Perl: open(...) or die "Could not open: $!". Any expression that returns false can have or die "message" chained to it. $! contains the operating system error string (errno). Ruby's equivalent raises exceptions directly from the method (File.open raises Errno::ENOENT) rather than returning false.Throwing exception objects
class AppError < StandardError
attr_reader :code
def initialize(message, code:)
super(message)
@code = code
end
end
begin
raise AppError.new("Not found", code: 404)
rescue AppError => error
puts "Error #{error.code}: #{error.message}"
end use v5.38;
package AppError;
sub new {
my ($class, %args) = @_;
return bless { message => $args{message}, code => $args{code} }, $class;
}
sub message { $_[0]->{message} }
sub code { $_[0]->{code} }
package main;
eval {
die AppError->new(message => "Not found", code => 404);
};
if (my $error = $@) {
if (ref $error && $error->isa("AppError")) {
say "Error " . $error->code . ": " . $error->message;
} else {
say "Unknown: $error";
}
} die can throw any value, including a blessed object β Perl's equivalent of raising a custom exception class. The ref $error check confirms it is a reference (not a plain string), and isa checks the class hierarchy. Ruby's rescue AppError => e handles the type check automatically; Perl requires manual inspection of $@.File I/O
Reading a file
File.open("/etc/hostname") do |file|
file.each_line do |line|
puts line.chomp
end
end use v5.38;
open(my $fh, '<', '/etc/hostname') or die "Cannot open: $!";
while (my $line = <$fh>) {
chomp $line;
say $line;
}
close $fh; The three-argument form of
open is always preferred: mode and filename are separate arguments, preventing a class of injection vulnerabilities. <$fh> reads one line at a time in a while loop β the canonical Perl idiom for line-by-line processing. chomp strips the trailing newline. Ruby's File.open with a block automatically closes the file; Perl requires explicit close.Writing a file
lines = ["First line", "Second line", "Third line"]
File.open("/tmp/output.txt", "w") do |file|
lines.each { |line| file.puts line }
end
puts "Done" use v5.38;
my @lines = ("First line", "Second line", "Third line");
open(my $out, '>', '/tmp/output.txt') or die "Cannot write: $!";
for my $line (@lines) {
say $out $line; # say with filehandle β no comma!
}
close $out;
say "Wrote " . scalar(@lines) . " lines"; > truncates and writes; >> appends. say $fh $data and print $fh $data accept a filehandle as the first argument with no comma β an unusual Perl syntax. Ruby uses file.puts line (method call syntax). File::Slurp or Path::Tiny from CPAN simplify file I/O the way Ruby's File.write and File.read do.File test operators
path = "/etc/hostname"
puts File.exist?(path)
puts File.file?(path)
puts File.directory?("/etc")
puts File.size(path) use v5.38;
my $path = "/etc/hostname";
say -e $path ? "exists" : "does not exist";
say -f $path ? "is a file" : "not a file";
say -d "/etc" ? "is a dir" : "not a dir";
say -r $path ? "readable" : "not readable";
say -s $path ? "non-empty" : "empty"; Perl file test operators are single or double-character operators prefixed with
-: -e (exists), -f (plain file), -d (directory), -r (readable), -s (non-zero size). Ruby's File.exist?, File.file?, File.directory? are the equivalents. Ruby borrowed its file test design from Perl.Special Variables
$_ β the default variable
words = %w[hello world perl ruby]
words.each { |word| puts word.upcase }
lengths = words.map(&:length)
puts lengths.join(" ") use v5.38;
my @words = qw(hello world perl ruby);
for (@words) { # $_ is the implicit loop variable
say uc; # uc() operates on $_ by default
}
my @upper = map { uc } @words; # $_ implicit
my @lengths = map { length } @words;
say "@lengths"; $_ is Perl's implicit "topic" variable β the default argument for many built-in functions (print, chomp, length, uc, etc.) and the implicit loop variable in for and while (<>). Ruby's block parameters make this implicit topic explicit, but Perl one-liners rely heavily on $_ to keep code short.Process variables
puts $PROGRAM_NAME # or $0
puts ARGV.inspect
puts ENV["HOME"] use v5.38;
say "Script: $0"; # program name (like Ruby's $0)
say "Args: @ARGV"; # command-line args (like Ruby's ARGV)
say "Home: $ENV{HOME}"; # environment (like Ruby's ENV)
say "PID: $$"; # process ID (like Ruby's $$) $0 holds the script name, @ARGV holds command-line arguments, %ENV is the environment hash, and $$ is the process ID. Ruby's $PROGRAM_NAME (or $0), ARGV, ENV, and $$ are direct descendants β Ruby preserved Perl's naming conventions for these global variables.Error variables
begin
raise "Something went wrong"
rescue => error
puts error.message
end
# Ruby uses exception objects, not global variables use v5.38;
eval { die "Something went wrong
" };
say "Exception: $@" if $@; # $@ = last exception from eval
# $! = OS error string (set by failed system calls)
open(my $fh, '<', '/nonexistent_path_xyz');
say "OS error: $!" if $!;
# Reset $@ after handling
eval { 1 };
say "After clean eval, $@ is: '$@'"; $@ captures the value passed to die inside an eval block β Perl's exception variable. $! is the operating system error string (like C's errno), set automatically when a system call fails. Ruby uses exception objects caught by rescue instead of these global variables, but Errno exception classes correspond directly to $! values.