◈ Netsurfer / perl.gg / Argv File Slurp

Argv File Slurp

2024-09-02

Part 1: DOWN THE RABBIT HOLE

This page shows how to read an entire file into a single string using only the @ARGV array and the diamond operator. Standard file reading usually requires an explicit open, a read loop, and a close. This trick skips all of that.
my $content = do { local @ARGV = ($filename); <> };
That is the whole trick. You do not open a filehandle, you do not close it, and you do not manage a variable for the handle. You just let Perl treat your filename like a command-line argument.

Is this a good idea for production code? Probably not. Is it a fascinating look at how Perl handles arguments? Absolutely.

Part 2: HOW THE MAGIC WORKS

We will break this trick down into three small pieces so you can see what each part actually does.
do { ... }
The do block runs the code inside it and returns the value of the last expression. It also gives us a clean scope to work in.
local @ARGV = ($filename);
This is where the trick happens. We temporarily swap @ARGV for a single-element list that holds our filename. The local keyword guarantees that Perl restores the original array when we leave the block.
<>
The diamond operator reads from whatever files are listed in @ARGV when you use it without a filehandle. We just told it there is exactly one file to read.

When Perl sees the assignment to $content, it runs <> in list context and slurps the whole file. The result flows back through the do block and lands in $content.

You have basically convinced Perl that your script started with your filename typed at the command line.

Part 3: CONTEXT MATTERS

The diamond operator changes its behavior depending on the surrounding assignment:
# 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!
That last line looks like scalar context at first glance, so why does it still slurp the whole file?

We are inside a do block, and Perl evaluates the expression based on the assignment that wraps it. The do block pushes that context inward. When you assign to a scalar, Perl keeps the last value from the list, but <> still reads every line.

If you actually want exactly one line, force scalar context explicitly:

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

Part 4: PRACTICAL APPLICATIONS

You can skip the party tricks and use this pattern where it actually saves you work.

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 are inside a tight scope and do not want to manage filehandle cleanup, this pattern stays self-contained:

sub get_template { my ($name) = @_; return do { local @ARGV = ("templates/$name.tmpl"); <> }; }
You do not leak filehandles. You do not forget to close them. The block cleans up after itself.

Part 5: PROCESSING FILES SEQUENTIALLY

This is where @ARGV manipulation really shines, since you can feed Perl a whole batch of files at once:
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 @ARGV array holds the current filename while Perl opens each file in sequence.

For per-file analysis with a reset between files:

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 paired with eof is another piece of older Perl wisdom. It detects when you have finished reading one file and closes the handle, which resets $. for the next file.

Part 6: THE DARK SIDE

Before you rewrite your entire file reading pipeline, keep these caveats in mind.
  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 will usually want a proper open() call with explicit error handling.
  1. BINARY FILES ARE RISKY

The diamond operator reads line by line. Binary files that contain embedded newlines will confuse it. Stick to open() with binmode() for those.

  1. IT'S OBSCURE

The next person reading your code, including your future self, might not immediately see what is happening. Sometimes boring is better.

Part 7: THE HYBRID APPROACH

If you want the brevity without the obscurity, reach for File::Slurp:
use File::Slurp qw(read_file); my $content = read_file($filename);
Or use Path::Tiny in modern Perl:
use Path::Tiny; my $content = path($filename)->slurp;
But where is 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 will not make your code clearer. It will not make it faster. It will just make you appreciate how malleable Perl's internals really are.

The @ARGV file slurp is the kind of technique you probably should not use in production code. Understanding it, though, is how you move from knowing Perl to thinking in Perl.

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

perl.gg

✓ Document: Donehidden-gems / 10.1 KiB▧