╔═╗╔═╗╦ ╔═╗ ╔═╗╦ ╦╦═╗╔═╗╔═╗╦═╗╦ ╦ ╚═╗║╣ ║ ╠╣───╚═╗║ ║╠╦╝║ ╦║╣ ╠╦╝╚╦╝ ╚═╝╚═╝╩═╝╚ ╚═╝╚═╝╩╚═╚═╝╚═╝╩╚═ ╩ ============================================================================ 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. #!/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 <>; 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. ============================================================================ PART 1: THE VISIBLE OUTPUT -------------------------- say "Count: 0"; 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. The script rewrites itself. Next time you open the file, it says: say "Count: 1"; And after that: say "Count: 2"; The source code on disk changes. Not a variable. The actual file. ============================================================================ PART 2: SLURP MODE ------------------ $/=undef; The $/ variable is the input record separator. Normally it's a newline, so <> reads one line at a time. 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 ------------------------ my $self = do { local @ARGV = ($0); <> }; This is dense. Let's unpack it: 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 So we're: 1. Temporarily setting @ARGV to our own filename 2. Reading the entire file (slurp mode, remember?) 3. Storing our own source code in $self The script just read itself into memory. .--. |o_o | |:_/ | // \ \ (| | ) /'\_ _/`\ \___)=(___/ ============================================================================ PART 4: THE SURGERY ------------------- $self =~ s~Count: \K\d+~$& + 1~e; This is where the magic happens. Breaking it down: 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 \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: 1. Find "Count: 5" 2. \K forgets "Count: " 3. $& contains just "5" 4. Replacement is 5 + 1 = 6 5. Result: "Count: 6" The string $self now contains our modified source code. ============================================================================ PART 5: IN-PLACE EDITING SETUP ------------------------------ @ARGV = ($0); $^I = qq|.${\time}.bak|; 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) Set up to process our own file $^I = "..." Enable in-place editing with backup suffix The $^I variable is the in-place edit switch. When set, Perl: 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 The backup suffix is: qq|.${\time}.bak| Which evaluates to something like ".1704312000.bak" - a Unix timestamp. Every run creates a new backup. ============================================================================ PART 6: THE WRITE ----------------- say $self while <>; This looks weird. Why the while loop? 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: script.pl Contains "Count: 0" After first run: script.pl Contains "Count: 1" script.pl.1704312000.bak Contains "Count: 0" After second run: script.pl Contains "Count: 2" script.pl.1704312001.bak Contains "Count: 1" script.pl.1704312000.bak Contains "Count: 0" You get a complete history. Time travel for code. ============================================================================ 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: * 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 But understanding HOW it works teaches you: * Perl's special variables ($0, $/, $^I, $&) * In-place editing mechanics * The \K regex anchor * The /e modifier * File handle manipulation Knowledge is never wasted. Just don't ship this to production. ============================================================================ PART 10: PRACTICAL APPLICATIONS ------------------------------- "But when would I actually use this?" Almost never. But similar techniques are useful for: * Build scripts that update version numbers * Config file processors * Log file rotation * Template systems * Code generators 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. ============================================================================ ___ / \ | o o | \ - / ___/ \___ / '---' \ | CAUTION | | SELF-MOD | | CODE | \___________/ ============================================================================ japh.codes