perl.gg / regex

<!-- category: regex -->

The /ee Double-Eval Modifier

2026-03-19

You know /e. It treats the replacement side of s~~~ as code instead of a string:
my $text = "price: 100"; $text =~ s~(\d+)~$1 * 1.13~e; # "price: 113"
Standard stuff. The /e flag evaluates $1 * 1.13 as an expression. Useful, well-understood.

Now double it.

$text =~ s~(\d+)~'$1 * 1.13'~ee;
First eval produces the string "100 * 1.13". Second eval executes that string as Perl code. Result: 113. A two-stage pipeline inside a regex.

This is /ee. A template engine in one substitution. Let's explore what it does, and why you should be a little scared of it.

Part 1: HOW /ee ACTUALLY WORKS

Be precise about the two stages:
my $x = 10; my $text = "result is VALUE"; $text =~ s~VALUE~'$x * 2'~ee; # $text is now "result is 20"
Stage 1 (first e): The replacement '$x * 2' is evaluated as Perl code. Single-quoted string, so no interpolation. Produces the literal string $x * 2.

Stage 2 (second e): That string $x * 2 is evaluated as Perl code. $x is 10. 10 * 2 is 20.

Replacement: '$x * 2' First eval: '$x * 2' --> string: $x * 2 Second eval: $x * 2 --> number: 20 Final result: "result is 20"
The two stages let you construct code dynamically in the first pass and execute it in the second. Powerful. Dangerous.

Part 2: VARIABLE INTERPOLATION IN TEMPLATES

The classic use case. Template strings where variable names appear literally:
my $name = "Alice"; my $count = 3; my $template = 'Hello $name, you have $count items'; $template =~ s~(\$\w+)~$1~gee; # "Hello Alice, you have 3 items"
Stage 1 evaluates $1, which produces the matched string (e.g., $name). Stage 2 evaluates that string as Perl code, looks up the variable, returns "Alice".

A rudimentary template engine in one line. The regex finds things that look like variables, extracts their names, and resolves them against the current scope.

Part 3: CONFIG FILE VARIABLE EXPANSION

Real-world pattern. A config file with variable references:
base_dir = /opt/app log_dir = $base_dir/logs data_dir = $base_dir/data cache_dir = $data_dir/cache
Parse and resolve with single /e (no /ee needed here):
my %config; while (my $line = <$fh>) { chomp $line; next if $line =~ m~^\s*#~ || $line =~ m~^\s*$~; my ($key, $val) = split m~\s*=\s*~, $line, 2; # Resolve variable references $val =~ s~\$(\w+)~$config{$1} // "\$$1"~ge; $config{$key} = $val; }
That's single /e with a hash lookup. Safe, predictable. For 90% of config file work, this is what you want.

But what if the config supports expressions?

width = 1920 height = 1080 pixels = $width * $height
Now you'd need to resolve variables first, then evaluate the math. Two stages. That's where /ee starts whispering in your ear.

Part 4: THE SECURITY PROBLEM

Giant letters time. /ee evaluates arbitrary strings as Perl code.

If any part of the matched text comes from user input, you have a code injection vulnerability. Not "maybe." Not "in theory." Right now. Game over.

# DANGEROUS: user input gets executed as code my $user_input = 'system("rm -rf /")'; my $template = 'value is $user_input'; $template =~ s~(\$\w+)~$1~gee; # You just executed arbitrary system commands
This is the exact class of vulnerability that has ended careers. The /ee modifier is eval in disguise, and eval on untrusted input is the oldest sin in the book.

Rules for /ee:

1. Never use /ee on strings containing user input. Ever. 2. If you think the input is sanitized, reread rule 1. 3. Use a hash lookup with single /e instead. 4. If you need expression evaluation, use a safe evaluator module.
The safe version:
# SAFE: hash lookup, no code execution $template =~ s~\$\{(\w+)\}~$vars{$1} // ''~ge;
Values come from your hash, which you control. No injection possible.

Part 5: DYNAMIC CODE GENERATION

Where /ee genuinely shines is metaprogramming where you control both the pattern and the data. Generating code from code.

Here's a dispatch table builder:

#!/usr/bin/env perl use strict; use warnings; use feature 'say'; my %op_sym = (add => '+', subtract => '-', multiply => '*'); my %dispatch; for my $op (keys %op_sym) { my $sym = $op_sym{$op}; $dispatch{$op} = eval "sub { \$_[0] $sym \$_[1] }"; } say $dispatch{add}->(3, 4); # 7 say $dispatch{multiply}->(5, 6); # 30
This uses plain eval rather than /ee, but the principle is identical: constructing code as strings and executing them. The /ee modifier is just a way to do this inline within a substitution. It's eval with a delivery mechanism.

Part 6: PRACTICAL NUMBER FORMATTING

Here's a benign, useful example with single /e. Formatting numbers in a report string:
my $report = "Revenue: 1234567.89 dollars, 987654 units sold"; $report =~ s~(\d+)(?=\.\d+|\b)~ my $n = $1; 1 while $n =~ s~(\d)(\d{3}(?:,\d{3})*(?:\.\d+)?$)~$1,$2~; $n; ~ge; # "Revenue: 1,234,567.89 dollars, 987,654 units sold"
Single /e with a multi-line replacement block. You don't need /ee because you're writing the code directly, not constructing it from strings. This is the typical real-world situation.

The /ee modifier is for when the code doesn't exist yet at compile time and you need to build it from match results first.

Part 7: UNDERSTANDING THE SECOND EVAL

The second e is equivalent to wrapping the first result in eval:
# These are equivalent: $str =~ s~PATTERN~REPLACEMENT~ee; $str =~ s~PATTERN~ eval(do { REPLACEMENT }) ~e;
All of eval's behaviors apply. Syntax error in the second stage? eval catches it, sets $@, and the substitution produces an empty string. Silently.
my $text = "value is CODE"; $text =~ s~CODE~'this is not } valid { perl'~ee; # $text is now "value is " # $@ contains the syntax error
No warning. No die. Just silent failure. If you're debugging /ee substitutions, check $@ after each one.

Part 8: THE TRIPLE /eee (DON'T)

Yes, you can stack them. /eee evaluates three times. First eval produces a string, second eval executes it to produce another string, third eval executes that.
my $x = 42; my $text = "answer"; $text =~ s~answer~'"\\$x"'~eee;
In practice, nobody uses /eee. If you need three levels of evaluation, you don't have a regex problem. You have an architecture problem. Or a drinking problem. Possibly both.

The practical ceiling is /ee, and even that is rare.

Part 9: SAFER ALTERNATIVES

Before reaching for /ee, try these:

Hash lookup with /e (90% of template cases):

$text =~ s~\$\{(\w+)\}~$vars{$1} // ''~ge;
Subroutine call with /e (complex logic):
sub resolve { my ($key) = @_; return $config{$key} if exists $config{$key}; return "[unknown: $key]"; } $text =~ s~\$(\w+)~resolve($1)~ge;
Template modules (real templating):
use Template::Tiny; my $tt = Template::Tiny->new; $tt->process(\$template, \%vars, \$output);
Each of these is safer, more readable, and more maintainable. Use them. Save /ee for the genuinely rare case where you need to evaluate dynamically constructed Perl code inline, and you have total control over the input.

Part 10: RESPECTING THE POWER

The /ee modifier exists because Perl respects you enough to hand you live ammunition. It doesn't hide dangerous features behind warnings and advisory committees. It gives you the tool and trusts you to point it in the right direction.

That trust is the essence of Perl. The language gives you eval, /ee, symbolic references, and a dozen other ways to shoot yourself in the foot. It also gives you strict, warnings, taint mode, and decades of production wisdom saying when to use each one.

Know it exists. Understand how it works. Use it when nothing else fits. And never, ever point it at user input.

.--. |o_o | |:_/ | // \ \ (| | ) /'\_ _/`\ \___)=(___/ /e = replacement is code /ee = replacement is code that produces code /eee = you've gone too far
perl.gg