Ruby.CodeCompared.To/Perl

An interactive executable cheatsheet for Rubyists learning Perl

Ruby 4.0 Perl 5.40
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.