The /e and /r Combo in Map
This page shows how to pair the/e and /r flags inside a map so you can run Perl code in your replacements while keeping your source data untouched.
That single line walks throughmy @doubled = map { s~(\d+)~$1 * 2~er } @prices;
@prices, finds every number, doubles it, and returns a fresh list. The original @prices stays exactly as it was.
Two flags handle the work here. The /e flag tells Perl to treat the replacement side as executable code instead of a static string. The /r flag returns a modified copy rather than changing the variable you are bound to. When you place both flags inside map, you get safe functional transformations that still carry full computational power.
Part 1: THE /r FLAG REFRESHER
Normallys~~~ modifies the variable it is bound to and returns the substitution count:
Withmy $text = "price: 50"; my $count = ($text =~ s~50~100~); # $count is 1, $text is now "price: 100"
/r, it returns the modified string and leaves the original alone:
The lettermy $text = "price: 50"; my $new = ($text =~ s~50~100~r); # $new is "price: 100", $text is still "price: 50"
r stands for non-destructive, which the official documentation labels as "return". The original variable is never touched and you receive a clean copy instead.
Part 2: THE /e FLAG REFRESHER
Normally the replacement side ofs~~~ is just a literal string:
Withmy $text = "count: 5"; $text =~ s~5~10~; # literal replacement
/e, the replacement becomes executable Perl code:
The capturedmy $text = "count: 5"; $text =~ s~(\d+)~$1 * 2~e; # $text is now "count: 10"
$1 holds the value "5". The expression $1 * 2 evaluates to 10. The match then gets replaced with that computed result.
This is arbitrary Perl code. You can call functions, run conditionals, or do math, and the substitution will use whatever the expression returns:
$text =~ s~(\w+)~uc($1)~eg; # uppercase every word $text =~ s~(\d+)~sprintf("%04d", $1)~eg; # zero-pad numbers $text =~ s~(error|warn)~"[$1]"~egi; # bracket keywords
Part 3: COMBINING /e AND /r
Put them together and you get evaluated replacements that return copies:The original stays preserved. The replacement was computed rather than pasted literally. Both flags run their jobs at the same time.my $original = "Item costs 25 dollars"; my $inflated = ($original =~ s~(\d+)~$1 * 1.15~er); # $original: "Item costs 25 dollars" # $inflated: "Item costs 28.75 dollars"
The flag order does not matter. s~~~er and s~~~re produce identical results. The sequence er simply reads better because your brain naturally expects the evaluate step to come before the return step.
Part 4: THE MAP CONNECTION
Here is where the pattern becomes useful. Insidemap, the default variable $_ is aliased to each element of the source list. If you mutate $_ directly, you mutate the original data.
Dangerous:
You changed the originals. That outcome is almost never what you want inside amy @names = ('alice', 'bob', 'charlie'); my @upper = map { s~^(\w)~uc($1)~e; $_ } @names; # @names is NOW ('Alice', 'Bob', 'Charlie') -- MUTATED!
map.
Safe:
Themy @names = ('alice', 'bob', 'charlie'); my @upper = map { s~^(\w)~uc($1)~er } @names; # @names is still ('alice', 'bob', 'charlie') # @upper is ('Alice', 'Bob', 'Charlie')
/r flag turns the operation functional. No mutation occurs. The map builds a new list from the returned copies and the source list survives intact.
Part 5: PRICE DOUBLING
You have a list of price strings and you want to double every number inside them:Result:my @prices = ( 'Widget: $25', 'Gadget: $50', 'Doohickey: $12.99', ); my @doubled = map { s~([\d.]+)~$1 * 2~ger } @prices;
TheWidget: $50 Gadget: $100 Doohickey: $25.98
/g flag handles multiple numbers per string. The /e flag performs the math. The /r flag protects the originals. Three flags and zero side effects.
Part 6: UNIT CONVERSION
Convert temperatures in text from Fahrenheit to Celsius:Result:my @readings = ( 'Lab A: 72F', 'Lab B: 68F', 'Lab C: 75F', ); my @celsius = map { s~(\d+)F~ sprintf("%.1fC", ($1 - 32) * 5/9) ~er } @readings;
The replacement side runs a fullLab A: 22.2C Lab B: 20.0C Lab C: 23.9C
sprintf call. The /e flag does not care how complex your expression is. If it is valid Perl, it runs.
Convert bytes to human-readable sizes:
Result:my @sizes = ('file1: 1048576', 'file2: 5368709120', 'file3: 1024'); my @human = map { s~(\d+)$~ my $b = $1; $b >= 1073741824 ? sprintf("%.1fG", $b/1073741824) : $b >= 1048576 ? sprintf("%.1fM", $b/1048576) : $b >= 1024 ? sprintf("%.1fK", $b/1024) : "${b}B" ~er } @sizes;
A full conditional expression sits inside the replacement. Perl processes it without complaint.file1: 1.0M file2: 5.0G file3: 1.0K
Part 7: DATE REFORMATTING
You have dates in MM/DD/YYYY format and you want ISO 8601:Result:my @dates = ('01/15/2026', '12/25/2025', '03/29/2026'); my @iso = map { s~(\d{2})/(\d{2})/(\d{4})~"$3-$1-$2"~er } @dates;
The captures2026-01-15 2025-12-25 2026-03-29
$1, $2, $3 grab month, day, and year. The replacement rearranges them. The /e flag evaluates the double-quoted string so the variables interpolate. The /r flag returns the copy.
Part 8: CHAINING WITH GREP
Combinemap with grep for filter-and-transform pipelines:
Result:my @log_lines = ( '2026-03-28 ERROR: disk usage 95%', '2026-03-28 INFO: backup complete', '2026-03-28 ERROR: memory usage 88%', '2026-03-28 INFO: cron job started', ); my @alerts = map { s~(\d+)%~"$1% (CRITICAL)" ~er } grep { m~ERROR~ } @log_lines;
Read the pipeline bottom to top. Filter for ERROR lines first, then transform the percentage values. The original2026-03-28 ERROR: disk usage 95% (CRITICAL) 2026-03-28 ERROR: memory usage 88% (CRITICAL)
@log_lines stays untouched. The output is a fresh list of modified copies.
Part 9: BUILDING DATA PIPELINES
Stack multiple transformations by nesting maps or chaining/r inside a single map:
Result:my @raw = ( ' ALICE::Engineering::75000 ', ' BOB::Sales::62000 ', ' CHARLIE::Marketing::58000 ', ); my @formatted = map { s~^\s+|\s+$~~gr # trim =~ s~^(\w)(\w+)~$1 . lc($2)~er # title case name =~ s~::~, ~gr # delimiters to commas =~ s~(\d+)$~ sprintf('$%s', commify($1)) ~er # format salary } @raw; sub commify { my $n = reverse $_[0]; $n =~ s~(\d{3})(?=\d)~$1,~g; return scalar reverse $n; }
Each step in the chain usesAlice, Engineering, $75,000 Bob, Sales, $62,000 Charlie, Marketing, $58,000
/r to pass its result to the next operation. The /e flag on specific steps handles the computation. The map collects the final results. Nothing gets mutated along the way.
Part 10: THE FUNCTIONAL ARGUMENT
Why does this matter? Because functional transforms are easier to reason about.When you see this:
You know three things immediately:my @results = map { s~(\d+)~$1 * 2~er } @input;
@inputis unchanged after this line@resultscontains modified copies- The transformation is self-contained
No spooky action at a distance. No hidden mutation. No wondering whether the loop body changed your source data. The /r flag acts as a contract that this substitution is read-only with respect to the original.
Compare that to the imperative approach:
Four lines to do what one line does. A manual copy. A mutable variable. A push. It all works, but it is just ceremony around a simple idea.my @results; for my $item (@input) { my $copy = $item; $copy =~ s~(\d+)~$1 * 2~e; push @results, $copy; }
The /er combo in map says exactly what it means: transform and return. Nothing more.
perl.gg@input | +---------+ | map | | s~~~er | +---------+ | @output Input unchanged. Output derived. Functional Perl. No mutation. .--. |o_o | |:_/ | // \ \ (| | ) /'\_ _/`\ \___)=(___/