File I/O & Text Processing

Perl Fundamentals — File I/O & Text Processing
Perl Fundamentals
Course 1 · Chapter 8 · File I/O & Text Processing

📄 File I/O & Text Processing

"Perl" originally stood for the Practical Extraction and Report Language — and this chapter is where that name pays off directly. Reading files line by line, pulling structured data out of raw text, and generating a report from it is the exact job Perl was built for, and it's very likely close to what you already reach for it for in real work.

📂 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.

⚠ Always Check open()'s Return Value

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);
⚠ No Comma Between the Filehandle and What You're Printing

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.

→ Solution

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 =~.

→ Solution

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.

→ Solution

🎯 What's Next

Next chapter: References — scalar/array/hash references, anonymous data structures, and arrow notation.