ZetCode

Perl substitution

last modified October 20, 2026

The s/// operator searches a string for a regular expression and replaces the matches. It is Perl's main tool for editing text: it can clean input, reformat data, expand templates and compute new values from the matched text.

Basic substitution

The operator s/PATTERN/REPLACEMENT/FLAGS searches a string for a pattern and replaces the match. It works on $_ or on the variable bound with =~ and changes the string in place. By default only the first match is replaced.

basic.pl
#!/usr/bin/perl

use strict;
use warnings;
use v5.34.0;

my $text = 'I like cats. Cats are great, cats are fun.';

(my $first = $text) =~ s/cats/dogs/;
say "first only: $first";

(my $all = $text) =~ s/cats/dogs/g;
say "all lower:  $all";

(my $nocase = $text) =~ s/cats/dogs/gi;
say "ignore case: $nocase";

my $copy = $text;
my $n = ($copy =~ s/cats/dogs/gi);
say "replacements made: $n";

my $none = 'nothing here';
my $zero = ($none =~ s/cats/dogs/);
say "no match returns: '", $zero, "'";

The first substitution replaces only the first lowercase cats, and with /g every occurrence is replaced. Adding /i makes the match case-insensitive, so Cats is replaced as well. The operator returns the number of replacements made, which is useful for counting. When nothing matches, it returns an empty string (false) and leaves the string unchanged.

$ perl basic.pl
first only: I like dogs. Cats are great, cats are fun.
all lower:  I like dogs. Cats are great, dogs are fun.
ignore case: I like dogs. dogs are great, dogs are fun.
replacements made: 3
no match returns: ''

Captures in the replacement

Parentheses in the pattern capture text that can be reused in the replacement through $1, $2 and so on. This allows reordering and wrapping parts of a string. Inside the pattern, \1 refers to the first capture.

captures.pl
#!/usr/bin/perl

use strict;
use warnings;
use v5.34.0;

my $name = 'Smith, John';
$name =~ s/^(\w+),\s*(\w+)$/$2 $1/;
say $name;

my $date = '2026-10-20';
$date =~ s/(\d+)-(\d+)-(\d+)/$3.$2.$1/;
say $date;

my $doubled = 'this is is a test test';
$doubled =~ s/\b(\w+)\s+\1\b/$1/g;
say $doubled;

my $wrap = 'important';
$wrap =~ s/^(.*)$/<b>$1<\/b>/;
say $wrap;

The first two substitutions reorder a name and a date. The third one uses the back-reference \1 inside the pattern to find a repeated word and replaces both copies with a single one. The last example wraps the whole string in tags. Note that the closing slash in </b> must be escaped, or a different delimiter used.

$ perl captures.pl
John Smith
20.10.2026
this is a test
<b>important</b>

The /e flag

With /e the replacement part is evaluated as Perl code and its result is inserted. The flag turns the replacement into an expression, so arithmetic and function calls are possible. A doubled /ee evaluates the result a second time.

expr.pl
#!/usr/bin/perl

use strict;
use warnings;
use v5.34.0;

my $prices = 'apple 3, pear 5, plum 12';

(my $doubled = $prices) =~ s/(\d+)/$1 * 2/ge;
say "doubled: $doubled";

my $text = 'hello big world';
(my $title = $text) =~ s/(\w+)/\u$1/g;
say "title case: $title";

(my $caps = $text) =~ s/(\w+)/ucfirst($1)/ge;
say "with function: $caps";

my $expr = 'compute: 2+3 and 4*5';
$expr =~ s/(\d+)([+*])(\d+)/"$1$2$3=" . eval "$1$2$3"/ge;
say $expr;

my $tpl = 'result is $value';
my $value = 42;
$tpl =~ s/(\$\w+)/$1/ee;
say $tpl;

The first substitution doubles every number. The \u escape upper-cases the next character in an ordinary replacement, while ucfirst does the same job with /e. In the calculation example the replacement builds a string and calls eval on the operands. With /ee the text $value is first produced as a string and then evaluated as a variable name. Use /ee only with trusted data, because it executes strings as code.

$ perl expr.pl
doubled: apple 6, pear 10, plum 24
title case: Hello Big World
with function: Hello Big World
compute: 2+3=5 and 4*5=20
result is 42

The /r flag

The /r flag returns the modified copy and leaves the original alone. It makes substitution usable in expressions, in map and in chains.

rflag.pl
#!/usr/bin/perl

use strict;
use warnings;
use v5.34.0;

my $original = '  padded text  ';

my $trimmed = $original =~ s/^\s+|\s+$//gr;
say "[$trimmed]";
say "[$original]";

my @files = qw(report.txt data.txt notes.txt);
my @renamed = map { s/\.txt$/.bak/r } @files;
say "@renamed";

say 'chained: ', 'a.b.c' =~ s/\./-/gr =~ s/-/+/r;

my @sorted = sort map { s/\W//gr } ('c-3', 'a-1', 'b-2');
say "@sorted";

The trimmed version is produced without touching the original, which is still padded. In map the flag avoids the usual trick of copying each element first. Because the result is a string, the operators can be chained, as in the a.b.c example, where the first call replaces the dots and the second one converts only the first hyphen.

$ perl rflag.pl
[padded text]
[  padded text  ]
report.bak data.bak notes.bak
chained: a+b-c
a1 b2 c3

Delimiters

Any non-whitespace character can serve as the delimiter. Choosing a different one avoids escaping slashes in paths and URLs. Bracketing delimiters such as {} allow whitespace and comments between the two parts when combined with /x.

delims.pl
#!/usr/bin/perl

use strict;
use warnings;
use v5.34.0;

my $path = '/usr/local/bin/perl';

(my $p1 = $path) =~ s/\/usr\/local/\/opt/;
say "escaped slashes: $p1";

(my $p2 = $path) =~ s{/usr/local}{/opt};
say "braces:          $p2";

(my $p3 = $path) =~ s|/usr/local|/opt|;
say "pipes:           $p3";

(my $p4 = $path) =~ s{
    /usr   # prefix
    /local # subdirectory
}{/opt}x;
say "with /x:         $p4";

my $str = 'cost: $5';
(my $p5 = $str) =~ s'\$5'$10';
say "single quote delimiter: $p5";

All four substitutions produce the same result, but the versions with braces or pipes need no backslashes. With /x the pattern part can be spread over several lines with comments. Using single quotes as the delimiter turns off interpolation in both parts, so $5 and $10 are literal text.

$ perl delims.pl
escaped slashes: /opt/bin/perl
braces:          /opt/bin/perl
pipes:           /opt/bin/perl
with /x:         /opt/bin/perl
single quote delimiter: cost: $10

Lookups and templates

Together with /e, a hash can drive the replacement. This is a compact way to expand abbreviations or fill in templates, and the defined-or operator // supplies a fallback for missing keys.

lookup.pl
#!/usr/bin/perl

use strict;
use warnings;
use v5.34.0;

my %abbrev = (
    btw => 'by the way',
    imo => 'in my opinion',
    fyi => 'for your information',
);

my $msg = 'btw, imo this is fine, fyi.';
(my $long = $msg) =~ s/\b(\w+)\b/$abbrev{lc $1} \/\/ $1/ge;
say $long;

my %vars = (name => 'Alice', day => 'Monday');
my $tpl = 'Hello {{name}}, see you on {{day}}. {{missing}}';
$tpl =~ s/\{\{(\w+)\}\}/$vars{$1} \/\/ "[$1?]"/ge;
say $tpl;

my $n = '1234567.891';
1 while $n =~ s/^(\d+)(\d{3})/$1,$2/;
say $n;

The first substitution replaces each word by its expansion if the hash has one, and keeps the word otherwise. The template example fills the {{name}} placeholders and marks the unknown one. The last loop inserts thousands separators by repeatedly applying the substitution until it no longer matches, using the statement modifier form 1 while ....

$ perl lookup.pl
by the way, in my opinion this is fine, for your information.
Hello Alice, see you on Monday. [missing?]
1,234,567.891

Substitution on lists and lines

A substitution applied to $_ works with for, so a whole array can be edited in place. The /m flag makes ^ match at the start of every line.

list.pl
#!/usr/bin/perl

use strict;
use warnings;
use v5.34.0;

my @lines = ('  one  ', ' two', 'three  ');

s/^\s+|\s+$//g for @lines;
say join '|', @lines;

my @words = qw(apple banana cherry);
s/a/A/ for @words;
say "@words";

my $text = "line1\nline2\nline3";
(my $indented = $text) =~ s/^/    /mg;
say $indented;

my $count = () = $text =~ /line/g;
say "lines: $count";

The first two loops modify the array elements directly because $_ is an alias. The s/^/ /mg call indents every line of a multi-line string. The final line counts matches with the () = ... idiom, since a match in list context returns all matches.

$ perl list.pl
one|two|three
Apple bAnana cherry
    line1
    line2
    line3
lines: 3

Best Practices

In this article we have covered the s/// operator: basic replacement, captures, the /g, /i, /e, /r and /m flags, alternative delimiters, and lookups.

Source

perlop: Regexp Quote-Like Operators

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Perl tutorials.