Self Surgery
What if a script could remember how many times you ran it?Not by writing to a database. Not by touching a config file. By rewriting its own source code.
Run it once: "Count: 0" - then the source code says "Count: 1" Run it again: "Count: 1" - source code becomes "Count: 2" Run it forever. The script keeps score.#!/usr/bin/env perl use feature 'say'; say "Count: 0"; $/=undef; my $self = do { local @ARGV = ($0); <> }; $self =~ s~Count: \K\d+~$& + 1~e; @ARGV = ($0); $^I = qq|.${\time}.bak|; say $self while <>;
Part 1: THE VISIBLE OUTPUT
This is what you see when the script runs. But here's the trick: after the first run, this line doesn't say 0 anymore.say "Count: 0";
The script rewrites itself. Next time you open the file, it says:
And after that:say "Count: 1";
The source code on disk changes. Not a variable. The actual file.say "Count: 2";
Part 2: SLURP MODE
The $/ variable is the input record separator. Normally it's a newline, so <> reads one line at a time.$/=undef;
Set it to undef and Perl enters slurp mode. The next read operation grabs the entire file in one gulp.
We need the whole file because we're about to perform surgery on it.
Part 3: READING YOURSELF
This is dense. Let's unpack it:my $self = do { local @ARGV = ($0); <> };
So we're:PIECE WHAT IT DOES ---------------- ------------------------------------------ $0 Special variable: name of the current script local @ARGV Temporarily replace @ARGV @ARGV = ($0) Set @ARGV to contain just our script name <> Diamond operator reads from files in @ARGV do { ... } Execute block and return last expression
The script just read itself into memory.1. Temporarily setting @ARGV to our own filename 2. Reading the entire file (slurp mode, remember?) 3. Storing our own source code in $self
.--. |o_o | |:_/ | // \ \ (| | ) /'\_ _/`\ \___)=(___/
Part 4: THE SURGERY
This is where the magic happens. Breaking it down:$self =~ s~Count: \K\d+~$& + 1~e;
The \K is the clever bit. It matches "Count: " but then says "forget I matched that." Only the digits end up in $& (the match variable). So if the file contains "Count: 5", we:PIECE WHAT IT DOES ----------- -------------------------------------------- s~~~ Substitution (using ~ as delimiter) Count: Match the literal text "Count: " \K "Keep" - don't include previous match in $& \d+ Match one or more digits (this becomes $&) $& + 1 Take matched digits, add one e Evaluate replacement as Perl code
The string $self now contains our modified source code.1. Find "Count: 5" 2. \K forgets "Count: " 3. $& contains just "5" 4. Replacement is 5 + 1 = 6 5. Result: "Count: 6"
Part 5: IN-PLACE EDITING SETUP
Now we need to write the modified code back to the file. Perl has a built-in feature for this: in-place editing.@ARGV = ($0); $^I = qq|.${\time}.bak|;
The $^I variable is the in-place edit switch. When set, Perl:@ARGV = ($0) Set up to process our own file $^I = "..." Enable in-place editing with backup suffix
The backup suffix is:1. Renames the original file (adding the suffix as backup) 2. Opens the original filename for writing 3. Reads from the backup, writes to the new file
Which evaluates to something like ".1704312000.bak" - a Unix timestamp. Every run creates a new backup.qq|.${\time}.bak|
Part 6: THE WRITE
This looks weird. Why the while loop?say $self while <>;
The diamond operator <> triggers the in-place editing machinery. Each read advances through the file. We're not using what we read - we're using it as a trigger to write our modified $self.
Since we're in slurp mode, <> reads everything at once. The while loop runs once, prints $self once, and we're done.
The file now contains our modified source code.
Part 7: WHAT HAPPENS ON DISK
Before first run:After first run:script.pl Contains "Count: 0"
After second run:script.pl Contains "Count: 1" script.pl.1704312000.bak Contains "Count: 0"
You get a complete history. Time travel for code.script.pl Contains "Count: 2" script.pl.1704312001.bak Contains "Count: 1" script.pl.1704312000.bak Contains "Count: 0"
Part 8: THE ANNOTATED VERSION
#!/usr/bin/env perl use feature 'say'; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # This line gets modified each run # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ say "Count: 0"; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Enable slurp mode # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Set input record separator to undef # Now <> reads entire files at once $/=undef; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Read our own source code # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # $0 is the script's filename # Temporarily set @ARGV so <> reads from ourselves my $self = do { local @ARGV = ($0); <> }; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Increment the counter # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # \K means "keep" - forget everything matched before this point # $& contains just the digits # The /e flag evaluates $& + 1 as Perl code $self =~ s~Count: \K\d+~$& + 1~e; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Set up in-place editing # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Process our own file @ARGV = ($0); # Enable in-place edit with timestamped backup $^I = qq|.${\time}.bak|; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Write modified source back # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # The while <> triggers in-place editing # We print our modified $self instead of the original content say $self while <>;
Part 9: THE DANGER ZONE
Self-modifying code is a party trick, not a production pattern.Problems:
But understanding HOW it works teaches you:* Version control hates it (constant uncommitted changes) * Debugging is a nightmare (which version are you running?) * One bug and you corrupt your own source * Security scanners will flag it (rightfully) * Other developers will hunt you down
Knowledge is never wasted. Just don't ship this to production.* Perl's special variables ($0, $/, $^I, $&) * In-place editing mechanics * The \K regex anchor * The /e modifier * File handle manipulation
Part 10: PRACTICAL APPLICATIONS
"But when would I actually use this?"Almost never. But similar techniques are useful for:
The in-place editing pattern ($^I with @ARGV) shows up in real tools. The self-modification part? Keep it in your bag of tricks for when you need to impress (or horrify) someone.* Build scripts that update version numbers * Config file processors * Log file rotation * Template systems * Code generators
perl.gg___ / \ | o o | \ - / ___/ \___ / '---' \ | CAUTION | | SELF-MOD | | CODE | \___________/