perl.gg / regex

Loops With Regex

2025-12-27

What if you could iterate over a hash without writing a for loop?

Not with while. Not with foreach. With regex substitution.

my $db = { name => 'Mike', age => '45' }; qq|@{[ reverse sort keys %{$db} ]}| =~ s~\S+~print qq|$&: $db->{$&}\n|~ger;
Output:
name: Mike age: 45
No loop keyword anywhere. Just a string, a regex, and the /e modifier doing things it probably shouldn't.

Part 1: THE NORMAL WAY

Before we go weird, here's how sane people do it:
for my $key (reverse sort keys %{$db}) { print qq|$key: $db->{$key}\n|; }
Clean. Readable. Maintainable. Everything your code should be.

Now let's throw that out the window.

Part 2: THE DATA

my $db = { name => 'Mike', age => '45' };
Note: $db is a REFERENCE to a hash, not a hash itself.
$db Hash reference (scalar) %{$db} Dereferenced hash keys %{$db} Keys of the dereferenced hash $db->{name} Value lookup through the reference
This distinction matters. We'll be dereferencing a lot.

Part 3: THE STRING TRICK

qq|@{[ reverse sort keys %{$db} ]}|
This builds a string containing all the hash keys. Let's unpack it:
PIECE WHAT IT DOES --------------------------- -------------------------------- qq|...| Double-quoted string @{[ ... ]} The "baby cart" operator keys %{$db} Get all keys from the hash sort Alphabetize them reverse Flip the order
The baby cart @{[...]} is the secret sauce. It:
1. Evaluates the code inside 2. Creates an anonymous array from the result 3. Immediately dereferences it into the string
So our string becomes:
"name age"
Just the keys, space-separated. Now we have something to regex against.

Part 4: THE SUBSTITUTION

=~ s~\S+~print qq|$&: $db->{$&}\n|~ger;
This is where the loop happens. Breaking it down:
PIECE WHAT IT DOES ------- ------------------------------------------------ =~ Bind the string to this regex operation s~~~ Substitution (using ~ as delimiter) \S+ Match one or more non-whitespace chars (a key) print ... The replacement (but it's code, not text) g Global - match ALL keys, not just the first e Evaluate replacement as Perl code r Return result, don't modify original
The /g flag is our loop. It finds every match in the string. The /e flag turns each replacement into code execution. For each key matched:
1. $& contains the matched key ("name" or "age") 2. The print statement runs 3. $db->{$&} looks up the value
Two keys, two matches, two print statements. Loop complete.
.--. |o_o | |:_/ | // \ \ (| | ) /'\_ _/`\ \___)=(___/

Part 5: THE MATCH VARIABLE

$&
This special variable holds whatever the regex just matched.

When \S+ matches "name", $& is "name". When \S+ matches "age", $& is "age".

We use it twice in the replacement:

print qq|$&: $db->{$&}\n| ^^ ^^^^^^^ | | | +-- Look up this key's value +------ Print the key itself
Same variable, two purposes. The key becomes both label and lookup.

Part 6: WALKTHROUGH

Starting string: "name age"
MATCH $& EXECUTES OUTPUT ----- ------ ----------------------------- ----------- 1 "name" print qq|name: $db->{name}\n| name: Mike 2 "age" print qq|age: $db->{age}\n| age: 45
The /g modifier keeps matching until the string is exhausted. Each match triggers the /e evaluation. Two matches, two prints.

Part 7: FILTERING WITH GREP

Want only specific keys? Add a grep:
qq|@{[ grep { m~^name$~ } keys %{$db} ]}| =~ s~\S+~print qq|$&: $db->{$&}\n|~ger;
The grep filters before the string is built:
PIECE WHAT IT DOES ----------------------- -------------------------------- keys %{$db} All keys: (name, age) grep { m~^name$~ } Keep only keys matching "name" Result Just: (name)
Now the string is just "name" and we only print one pair.

You can use any condition in the grep:

# Keys starting with 'n' grep { m~^n~ } keys %{$db} # Keys longer than 3 characters grep { length > 3 } keys %{$db} # Keys that aren't 'age' grep { $_ ne 'age' } keys %{$db}

Part 8: THE ANNOTATED VERSION

#!/usr/bin/env perl use feature qw|say|; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Our data structure # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Hash reference (not a hash) my $db = { name => 'Mike', age => '45' }; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # The regex "loop" # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Step 1: Build a string of all keys # @{[...]} interpolates code results into the string # reverse sort orders them Z-A # Step 2: Use s///ge to "iterate" # \S+ matches each key # /g makes it match ALL keys # /e evaluates the replacement as code # /r returns result without modifying original # Step 3: $& holds the current match (the key) # Use it to print key and look up value qq|@{[ reverse sort keys %{$db} ]}| =~ s~\S+~print qq|$&: $db->{$&}\n|~ger;

Part 9: WHY YOU SHOULDN'T DO THIS

Let's be clear: this is a terrible idea for production code.

Problems:

* Unreadable to anyone who hasn't seen this trick * Slower than a real loop (regex overhead) * $& has performance implications (though modern Perl is better) * Breaks if keys contain whitespace * Debugging is a nightmare
But it demonstrates:
* The baby cart operator @{[...]} * Regex as iteration with /g * Code execution in substitution with /e * The $& match variable * Reference dereferencing patterns
These are all useful concepts. Just... use them normally.

Part 10: WHEN THIS PATTERN IS USEFUL

The s///ge pattern (minus the weird key iteration) shows up in real code for transformations:
# Convert all numbers to their squares $text =~ s~\d+~$& * $&~ge; # Uppercase all words starting with 'a' $text =~ s~\ba\w+~uc($&)~ge; # Replace placeholders with values $template =~ s~\{\{(\w+)\}\}~$data->{$1}~ge;
The last one is basically a micro-templating engine. Same pattern, legitimate use case.

The baby cart trick is useful for embedding calculations in strings:

say "Sum: @{[ 2 + 2 ]}"; # Sum: 4 say "Time: @{[ scalar localtime ]}"; # Time: Sun Jan 5 14:30:00 2025 say "Files: @{[ `ls | wc -l` ]}"; # Files: 42
Know the techniques. Use them wisely.
.---. / o o \ \ L / ----- /| |\ / | | \ | | /| |\ (_| |_) "Who needs for loops anyway?"
perl.gg