perl.gg / hidden-gems

Argv File Slurp

2024-09-02

Part 1: DOWN THE RABBIT HOLE

Every Perl programmer knows about reading files. Open a filehandle, read the contents, close it. Simple, clean, boring.

But what if I told you there's a way to slurp an entire file in one line using nothing but @ARGV trickery and the diamond operator?

Behold:

my $content = do { local @ARGV = ($filename); <> };
That's it. One line. No explicit open. No close. No filehandle variable. Just pure, distilled Perl magic.

Is this a good idea? Probably not. Is it fascinating? Absolutely.

Part 2: HOW THE MAGIC WORKS

Let's dissect this beautiful monstrosity piece by piece.
do { ... }
The do block executes the code inside and returns the value of the last expression. It creates a scope for our local variable.
local @ARGV = ($filename);
Here's where it gets interesting. We're temporarily replacing @ARGV with a single-element list containing our filename. The 'local' keyword ensures this change is undone when we leave the block.
<>
The diamond operator! When used without a filehandle, it reads from the files listed in @ARGV. We've just told it there's exactly one "argument"

In list context, <> slurps the entire file. The result flows back through the do block and into $content.

It's like we've tricked Perl into thinking the script was invoked with our filename as a command-line argument.

Part 3: CONTEXT MATTERS

The diamond operator behaves differently based on context:
# Scalar context - reads one line my $line = do { local @ARGV = ($filename); <> }; # List context - slurps entire file my @lines = do { local @ARGV = ($filename); <> }; my $content = do { local @ARGV = ($filename); <> }; # Also list context!
Wait, that last one looks like scalar context. Why does it slurp?

Because we're inside a do block, and Perl evaluates the expression in the context of the assignment. The do block propagates context inward. When assigning to a scalar, Perl uses the last value from the list - but <> still reads everything.

If you want exactly one line:

my $first_line = do { local @ARGV = ($filename); scalar <> };

Part 4: PRACTICAL APPLICATIONS

Okay, party tricks aside, when might this actually be useful?

PROCESSING MULTIPLE FILES WITH PER-FILE ANALYSIS:

my @files = qw(data1.txt data2.txt data3.txt); for my $file (@files) { my $content = do { local @ARGV = ($file); <> }; my $line_count = ($content =~ tr/\n//); my $word_count = scalar(split /\s+/, $content); print "$file: $line_count lines, $word_count words\n"; }
QUICK ONE-LINER STYLE:
# Imagine this in a larger script where you need a quick slurp my $config = do { local @ARGV = ("$ENV{HOME}/.myapprc"); <> };
AVOIDING FILEHANDLE POLLUTION:

When you're in a tight scope and don't want to deal with filehandle management, this pattern is self-contained:

sub get_template { my ($name) = @_; return do { local @ARGV = ("templates/$name.tmpl"); <> }; }
No filehandle leaks. No forgotten closes. The block cleans up after itself.

Part 5: PROCESSING FILES SEQUENTIALLY

Here's where @ARGV manipulation really shines - batch processing:
my @logfiles = glob("logs/*.log"); # Process all files as if they were one stream local @ARGV = @logfiles; while (<>) { if (/ERROR/) { print "Found in $ARGV at line $.: $_"; } }
The magic variable $ARGV contains the current filename being processed. Perl automatically opens each file in sequence.

For per-file analysis with reset:

local @ARGV = @logfiles; my %stats; while (<>) { $stats{$ARGV}{lines}++; $stats{$ARGV}{errors}++ if /ERROR/; } continue { # Reset line counter between files close ARGV if eof; }
That 'continue' block with 'eof' is another piece of arcane Perl wisdom - it detects when we've finished a file and closes it, resetting $. for the next file.

Part 6: THE DARK SIDE

Before you go rewriting all your file I/O, some caveats:
  1. ERROR HANDLING IS TRICKY
# This won't die on missing file the way open() would my $content = do { local @ARGV = ("nonexistent.txt"); <> }; # $content is just undef, no error raised
For production code, you probably want proper open() with error handling.
  1. BINARY FILES ARE RISKY

The diamond operator does line-oriented reading. Binary files with embedded newlines will confuse it. Stick to open() with binmode().

  1. IT'S OBSCURE

The next person reading your code (including future you) might not immediately grok what's happening. Sometimes boring is better.

Part 7: THE HYBRID APPROACH

Want the brevity without the obscurity? Consider File::Slurp:
use File::Slurp qw(read_file); my $content = read_file($filename);
Or in modern Perl with Path::Tiny:
use Path::Tiny; my $content = path($filename)->slurp;
But where's the fun in that? Sometimes you want to reach into Perl's bag of tricks and pull out something delightfully weird. The @ARGV slurp is exactly that kind of trick.

It won't make your code clearer. It won't make it faster. But it will make you appreciate just how malleable Perl's internals really are.

The @ARGV file slurp is the kind of technique you probably shouldn't use in production code. But understanding it? That's how you level up from "knowing Perl" to "thinking in Perl."

Sometimes the journey through the weird parts is the whole point.

perl.gg