File I/O & Text Processing
๐ File I/O & Text Processing
๐ Opening and Closing a File
open(my $fh, '<', $filename) or die "Can't open $filename: $!"; # ... read from $fh ... close($fh);
This is the modern, 3-argument form of open โ filehandle, mode, filename, each as separate arguments. The modes: '<' for reading, '>' for writing (truncates the file first โ Course 4-equivalent caution applies), '>>' for appending. Always use the 3-argument form; the older 2-argument form (open($fh, "< $filename")) mixes the mode and filename into one string, which is a genuine security and correctness hazard if $filename ever contains unexpected characters.
Chapter 5's open(...) or die "..."; idiom isn't optional politeness โ without it, a script that fails to open a file (wrong path, missing permissions) continues running anyway, silently doing nothing useful with an unopened filehandle. This is a genuinely common source of "my script ran with no errors but produced no output" confusion. Every open call should be followed by or die (or equivalent error handling โ Course 2's Error Handling chapter covers alternatives).
๐ Reading Line by Line โ the while (<FH>) Idiom
open(my $fh, '<', 'access.log') or die "Can't open: $!"; while (my $line = <$fh>) { chomp($line); # removes the trailing newline โ $line includes it otherwise say $line; } close($fh);
<$fh> reads one line at a time from the filehandle โ this is the single most iconic Perl idiom, appearing in essentially every script that ever touches a file. Each line returned includes its trailing newline character; chomp removes exactly that one trailing newline (and only if present), which is why it's almost always the first thing done to a freshly-read line.
๐ฅ Reading an Entire File at Once
open(my $fh, '<', $filename) or die "Can't open: $!"; local $/; # undefine the input record separator โ "slurp mode" my $content = <$fh>; # reads the ENTIRE file into one scalar close($fh);
$/ is the special variable controlling where a line "ends" โ normally a newline. Temporarily undefining it (local $/;, using Chapter 2's local to restore it automatically afterward) makes <$fh> read the whole file in one call instead of one line at a time โ the classic "slurp" idiom. Course 2's CPAN chapter covers Path::Tiny, a module that wraps this pattern into a single tidy method call.
โ๏ธ Writing to a File
open(my $fh, '>', 'report.txt') or die "Can't open: $!"; print $fh "Report generated.\n"; # NO comma between $fh and the text! close($fh);
print $fh "text"; is correct โ print $fh, "text"; is a real, easy-to-make mistake. With the comma, Perl treats $fh as just another item in the list being printed to standard output, printing the filehandle's internal stringification followed by "text" โ not writing to the file at all. This asymmetric-looking syntax (a filehandle directly followed by the thing to print, no separator) is one of Perl's odder corners, but it's exactly how print is told which filehandle to target.
๐ง A Worked Example โ Processing a Log File
use strict; use warnings; use feature 'say'; sub count_errors { my ($log_file) = @_; my %error_counts; open(my $fh, '<', $log_file) or die "Can't open $log_file: $!"; while (my $line = <$fh>) { if ($line =~ /(ERROR|WARN|INFO)/) { $error_counts{$1}++; } } close($fh); return %error_counts; } my %counts = count_errors('app.log'); open(my $out, '>', 'summary.txt') or die "Can't open: $!"; foreach my $level (sort keys %counts) { print $out "$level: $counts{$level}\n"; } close($out);
This is a genuine, small end-to-end script combining nearly everything covered so far: a subroutine (Chapter 7) that reads a file line by line, uses regex capture groups (Chapter 6) to classify each line, tallies results in a hash (Chapter 4), and returns it context-appropriately โ followed by writing a sorted summary report to a new file. This is exactly the shape of real, everyday Perl text-processing work.
File Handling: Perl vs. Python
Python's with open(...) as f: context manager guarantees the file closes automatically when the block ends. Perl's lexical filehandles (my $fh) have a similar safety net built in, less obviously: since $fh is just an ordinary lexical scalar, the file it holds open closes automatically once $fh itself goes out of scope โ no special syntax required. That said, an explicit close($fh); remains standard practice in real code, since it makes the exact moment a resource is released clear to a reader, rather than relying on scope boundaries alone.
3-argument open
open($fh, '<', $filename) or die ...; โ always check the result.
while (<$fh>)
Reads one line at a time; chomp strips the trailing newline.
Slurp mode
local $/; reads an entire file into one scalar in one call.
print $fh "..."
No comma between the filehandle and what's being printed.
๐ป Coding Challenges
Challenge 1: Write Then Read
Write a script that opens a file for writing, writes 3 lines to it using print $fh, closes it, then reopens the same file for reading and prints each line (with chomp applied) using the while (<$fh>) idiom.
Challenge 2: Count Lines Matching a Pattern
Given a text file with several lines, write a subroutine count_matching(filename, pattern) that returns how many lines match a given regex pattern, using the while (<$fh>) idiom and =~.
Challenge 3: Fix the Missing or die
Given open(my $fh, '<', 'does_not_exist.txt'); with no error handling, explain in one sentence what happens when this line runs against a genuinely missing file, then rewrite it correctly using this chapter's or die idiom.
๐ฏ What's Next
Next chapter: References โ scalar/array/hash references, anonymous data structures, and arrow notation.