The /ee Double-Eval Modifier
This page explains how the/ee modifier works in Perl substitutions and when you should actually use it. The /e flag already tells Perl to run the replacement side as code instead of a literal string:
Standard stuff. Themy $text = "price: 100"; $text =~ s~(\d+)~$1 * 1.13~e; # "price: 113"
/e flag evaluates $1 * 1.13 as an expression. Useful, well-understood.
When you add a second e, Perl runs that code twice. The first pass builds a string, and the second pass executes that string as Perl code. This creates a two-stage pipeline inside a single substitution:
The modifier is a template engine in one substitution. It is also the kind of tool that deserves a healthy dose of caution.$text =~ s~(\d+)~'$1 * 1.13'~ee;
Part 1: HOW /ee ACTUALLY WORKS
The modifier runs the replacement string through Perl twice. The first pass evaluates the replacement as code and returns a string. The second pass takes that string and evaluates it as code again:Stage 1 (firstmy $x = 10; my $text = "result is VALUE"; $text =~ s~VALUE~'$x * 2'~ee; # $text is now "result is 20"
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.
The two stages let you construct code dynamically in the first pass and execute it in the second. This is a two-stage pipeline inside a regex. It is powerful, and it is dangerous.Replacement: '$x * 2' First eval: '$x * 2' --> string: $x * 2 Second eval: $x * 2 --> number: 20 Final result: "result is 20"
Part 2: VARIABLE INTERPOLATION IN TEMPLATES
The classic use case is template strings where variable names appear literally:Stage 1 evaluatesmy $name = "Alice"; my $count = 3; my $template = 'Hello $name, you have $count items'; $template =~ s~(\$\w+)~$1~gee; # "Hello Alice, you have 3 items"
$1, which produces the matched string (e.g., $name). Stage 2 evaluates that string as Perl code, looks up the variable, and returns "Alice". This is 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
Here is a real-world pattern. A config file with variable references:You can parse and resolve this with a singlebase_dir = /opt/app log_dir = $base_dir/logs data_dir = $base_dir/data cache_dir = $data_dir/cache
/e flag, and you do not need /ee here:
That is a singlemy %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; }
/e with a hash lookup. It is safe and predictable. For ninety percent of config file work, this is what you want.
But what if the config supports expressions?
You would need to resolve variables first, then evaluate the math. That requires two stages, and that is wherewidth = 1920 height = 1080 pixels = $width * $height
/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.
This is the exact class of vulnerability that has ended careers. The# 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
/ee modifier is eval in disguise, and eval on untrusted input is the oldest sin in the book.
Rules for /ee:
The safe version looks like this: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.
Values come from your hash, which you control. No injection is possible.# SAFE: hash lookup, no code execution $template =~ s~\$\{(\w+)\}~$vars{$1} // ''~ge;
Part 5: DYNAMIC CODE GENERATION
Where/ee genuinely shines is metaprogramming where you control both the pattern and the data. Generating code from code is the right home for this tool.
Here is a dispatch table builder:
This uses plain#!/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
eval rather than /ee, but the principle is identical. You are constructing code as strings and executing them. The /ee modifier is just a way to do this inline within a substitution. It is eval with a delivery mechanism.
Part 6: PRACTICAL NUMBER FORMATTING
Here is a benign, useful example with single/e. Formatting numbers in a report string:
Singlemy $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"
/e with a multi-line replacement block. You do not need /ee because you are writing the code directly, not constructing it from strings. This is the typical real-world situation.
The /ee modifier is for when the code does not exist yet at compile time and you need to build it from match results first.
Part 7: UNDERSTANDING THE SECOND EVAL
The seconde is equivalent to wrapping the first result in eval:
All of# These are equivalent: $str =~ s~PATTERN~REPLACEMENT~ee; $str =~ s~PATTERN~ eval(do { REPLACEMENT }) ~e;
eval's behaviors apply. A syntax error in the second stage causes eval to catch it, set $@, and return an empty string. The failure is silent.
There is no warning and nomy $text = "value is CODE"; $text =~ s~CODE~'this is not } valid { perl'~ee; # $text is now "value is " # $@ contains the syntax error
die. There is just silent failure. If you are debugging /ee substitutions, check $@ after each one.
Part 8: THE TRIPLE /eee (DON'T)
Yes, you can stack them./eee evaluates three times. The first eval produces a string, the second eval executes it to produce another string, and the third eval executes that final string.
In practice, nobody usesmy $x = 42; my $text = "answer"; $text =~ s~answer~'"\\$x"'~eee;
/eee. If you need three levels of evaluation, you do not have a regex problem. You have an architecture problem, or possibly a drinking problem. The practical ceiling is /ee, and even that is rare.
Part 9: SAFER ALTERNATIVES
Before reaching for/ee, try these approaches.
Hash lookup with /e handles ninety percent of template cases:
Subroutine call with /e handles complex logic:$text =~ s~\$\{(\w+)\}~$vars{$1} // ''~ge;
Template modules handle real templating:sub resolve { my ($key) = @_; return $config{$key} if exists $config{$key}; return "[unknown: $key]"; } $text =~ s~\$(\w+)~resolve($1)~ge;
Each of these is safer, more readable, and more maintainable. Use them. Saveuse Template::Tiny; my $tt = Template::Tiny->new; $tt->process(\$template, \%vars, \$output);
/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 does not 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.
perl.gg.--. |o_o | |:_/ | // \ \ (| | ) /'\_ _/`\ \___)=(___/ /e = replacement is code /ee = replacement is code that produces code /eee = you've gone too far