Substitution#
s/// is the search-and-replace operator. Its general form is
$target =~ s/pattern/replacement/modifiers;
It finds the first position where pattern matches in target,
replaces that match with replacement, and returns the number of
substitutions made. With /g, it does that for every non-overlapping
match.
my $x = "Time to feed the cat!";
$x =~ s/cat/hacker/; # $x is now "Time to feed the hacker!"
If the pattern does not match, the target is unchanged and s///
returns a false value.
Operating on $_#
Like m//, s/// defaults to $_ when no =~ is given:
for (@lines) {
s/\s+$//; # trim trailing whitespace from each line
}
Counting substitutions#
The return value of s/// in scalar context is the number of
replacements:
my $count = ($text =~ s/\btodo\b/done/gi);
print "cleared $count todos\n";
In boolean context, zero means no match.
The replacement string#
The replacement is a double-quoted string, so variables and escape sequences interpolate:
my $suffix = ".bak";
$file =~ s/$/$suffix/; # append .bak to the name
$x =~ s/\t/ /g; # each tab becomes four spaces
Inside the replacement, $1, $2, $&, $+ and friends refer to
what the current match just captured:
my $y = "'quoted words'";
$y =~ s/^'(.*)'$/$1/; # strip surrounding single quotes
# $y is now "quoted words"
Named captures work the same way through $+{name}:
"2026-04-23" =~ s/(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/$+{d}\/$+{m}\/$+{y}/;
# result: "23/04/2026"
Escaping in the replacement#
The replacement is interpreted as a double-quoted string, so the
double-quote rules apply to it, independently of the pattern.
Metacharacters like *, +, ?, $ retain their double-quoted
meanings — only $ and @ trigger interpolation:
$x =~ s/(\w+)/[$1]/g; # wrap every word in brackets
$x =~ s/\$/USD/g; # replace literal '$' with 'USD'
Backslash escape sequences in the replacement follow the
double-quote rules — \t, \n, \x{…} all work. The special
case-modification escapes \l, \u, \L, \U, \E, \Q apply
in the replacement too:
$x =~ s/(\w+)/\u$1/g; # capitalise the first letter of each word
$x =~ s/(\w+)/\U$1\E/g; # uppercase whole word
/g — replace all#
Without /g, s/// replaces only the first match. With /g, it
replaces every non-overlapping match from left to right:
my $x = "I batted 4 for 4";
$x =~ s/4/four/; # "I batted four for 4"
$x = "I batted 4 for 4";
$x =~ s/4/four/g; # "I batted four for four"
/g in substitution is unrelated to /g in matching. Here it just
means “do this again and again until the pattern stops matching.”
/e — evaluate the replacement#
Under /e, the replacement is Perl code, not a string. The code
runs for every match and its return value replaces the match.
my $x = "numbers: 1 2 3 4";
$x =~ s/(\d+)/$1 * 2/ge; # "numbers: 2 4 6 8"
The match variables $1, $2, … are visible inside the code block.
Practical uses:
# Uppercase the first letter of every sentence.
$text =~ s/(^|\.\s+)(\w)/$1\U$2/g;
# Hex-encode non-ASCII bytes.
$bytes =~ s/([\x80-\xff])/sprintf "\\x%02x", ord $1/ge;
# Apply a lookup table.
my %subst = (red => 0xff0000, green => 0x00ff00, blue => 0x0000ff);
$css =~ s/\b(red|green|blue)\b/sprintf "#%06x", $subst{$1}/ge;
/ee evaluates twice — first as code, then the result is itself
evaluated as Perl. Rarely the right tool; when it is, use it
sparingly and with care:
my $var = "greeting";
our $greeting = "hello";
"greeting" =~ s/(\w+)/\$$1/ee; # first: "\$greeting"; second: "hello"
/r — non-destructive substitution#
/r returns the new string instead of modifying the target. The
target is untouched:
my $orig = "I like dogs";
my $new = $orig =~ s/dogs/cats/r;
# $orig is still "I like dogs"
# $new is "I like cats"
If the pattern does not match, /r returns the original string
unchanged:
my $x = "I like dogs";
my $y = $x =~ s/elephants/cougars/r;
# $y eq "I like dogs"
The big win is chaining:
my $slug = $title
=~ s/[^\w\s-]//gr # drop punctuation
=~ s/\s+/-/gr # collapse spaces to dash
=~ s/--+/-/gr; # collapse runs of dashes
Each arrow returns a new string, which the next s///r receives.
Alternate delimiters#
Both halves of s/// can use matched brackets; the delimiters do
not have to be the same:
s{pattern}{replacement}g;
s<pattern><replacement>g;
s[pattern][replacement]g;
s(pattern)[replacement]g; # legal, but avoid mixing for clarity
And any printable punctuation works in a single-character form:
s!/usr/local/!/opt/!g;
s#pattern#replacement#;
Useful when the pattern or replacement contains /.
Single-quoted substitution#
s'pattern'replacement' treats both halves as single-quoted — no
variable interpolation, no backslash escapes apart from \' and
\\:
s'@users'@admins'g; # literal '@users' becomes literal '@admins'
Rarely needed; mention it for completeness.
Combining with /m, /s, /x#
Substitution modifiers compose with match modifiers, so you can get the full toolbox:
# Trim each line.
$x =~ s/^\s+|\s+$//mg;
# Collapse blank lines.
$x =~ s/\n{2,}/\n\n/g;
# Replace C-style comments across newlines.
$src =~ s{/\*.*?\*/}{}gs; # /s lets . cross newlines
With /x the pattern is readable, with /s it spans newlines,
with /g it repeats.
A real example#
Turn a block of English into a slug — lowercase, hyphens instead of spaces, drop non-letters, collapse and trim:
sub slug {
my ($s) = @_;
return lc($s)
=~ s/[^\w\s-]//gr # remove punctuation
=~ s/\s+/-/gr # spaces to hyphens
=~ s/-+/-/gr # collapse hyphens
=~ s/^-|-$//gr; # trim leading/trailing hyphens
}
print slug("Hello, World! -- Regex Tutorial");
# prints: hello-world-regex-tutorial
Things that look like substitution but aren’t#
tr/// (also written y///) does character-by-character
transliteration, not regexp substitution. It accepts two lists of
characters; each occurrence of the nth character in the first list
becomes the nth character in the second list. No regexp features
apply:
my $x = "Hello";
$x =~ tr/A-Za-z/a-zA-Z/; # swap case
(my $hex = $bytes) =~ tr/\x00-\xff//d; # delete all bytes — pointless
tr/// is faster than s/// for character-level work because it
is not a regexp engine. See tr for
full semantics.