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.