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.

See also#

  • s — substitution operator reference.

  • tr — character transliteration; not substitution but often confused for it.

  • quotemeta — escape a string for safe interpolation into a pattern.

  • The modifiers chapter — /g, /e, /r, /ee, and how they interact.