Skip to content
VLSI Mentor

Perl Part I · Module 13 · File Handling

or die "$!": Never Let open Fail Silently

Every open on the previous four pages ended with or die, and this is the chapter that explains why. The reason is not tidiness. An open that fails does not stop your program and does not print anything — it returns false and carries on, and section one measures what that leaves behind: a script that read zero lines from a missing file and concluded the file was empty. That is the worst possible outcome, because an empty report and a missing report lead to completely different actions, and the script cannot tell them apart. The chapter then looks at the special variable that holds the reason, shows three different real failures, and finishes with the thing about it that catches people out — it describes the most recent failure, not the one you meant to report.

Foundation11 min readPerlPart IFile Handling

Part I · Module 13 · Chapter 13.5 · or die "$!": Never Let open Fail Silently

1. What an Unchecked open Leaves Behind

Azvya Education Pvt. Ltd.VLSI Mentor
unchecked-open.pl
use strict;
use warnings;
use File::Temp qw(tempdir);

my $dir  = tempdir(CLEANUP => 1);
my $miss = "$dir/not-here.txt";

# UNCHECKED: no "or die". The program keeps going.
my @lines;
{
    no warnings;                     # so the page can show the OUTCOME
    open my $fh, "<", $miss;         # fails, and we ignore it
    @lines = <$fh>;                  # reads from an unopened handle
}
printf "unchecked : read %d line(s), and the program kept running\n", scalar @lines;
printf "            the script now believes the file was EMPTY\n";

# CHECKED: the failure is reported where it happens.
my $err = "";
eval {
    open my $fh, "<", $miss or die "cannot read $miss: $!\n";
    1;
} or $err = $@;
$err =~ s/\Q$dir\E/TMPDIR/;          # the temp path varies per run
printf "\nchecked   : %s", $err;
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
unchecked : read 0 line(s), and the program kept running
            the script now believes the file was EMPTY

checked   : cannot read TMPDIR/not-here.txt: No such file or directory

The unchecked version read zero lines and kept going.

Think about what the script does next. It has an empty list of lines. It will report zero violations, or write an empty summary, or conclude that the run was clean. All of those are plausible-looking results, and all of them are wrong — the file was not empty, it was not there.

This is the worst shape a bug can take. There is no error, no warning, and no crash. The output looks like a successful run of a design with no problems, which is exactly the answer everybody hopes for and nobody questions.

The checked version failed immediately and said what and why.

So the rule is simple and absolute: every open gets or die. The cost is eight characters.

2. What $! Says

$! is a special variable holding the reason the last system call failed. Different problems give different messages.

Azvya Education Pvt. Ltd.VLSI Mentor
what-bang-holds.pl
use strict;
use warnings;
use File::Temp qw(tempdir);

my $dir = tempdir(CLEANUP => 1);

# Make a file we are not allowed to read.
open my $w, ">", "$dir/locked.txt" or die $!;
print $w "secret\n"; close $w;
chmod 0000, "$dir/locked.txt";

my @cases = (
    [ "file does not exist",     "$dir/missing.txt",      "<" ],
    [ "parent dir is missing",   "$dir/no/such/x.txt",    ">" ],
    [ "no read permission",      "$dir/locked.txt",       "<" ],
);

for my $c (@cases) {
    my ($label, $path, $mode) = @$c;
    if (open my $fh, $mode, $path) { close $fh; printf "%-22s : opened\n", $label }
    else                           { printf "%-22s : %s\n", $label, $! }
}
chmod 0600, "$dir/locked.txt";       # so the temp dir can be cleaned up
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
file does not exist    : No such file or directory
parent dir is missing  : No such file or directory
no read permission     : Permission denied

Three failures, and the messages distinguish two genuinely different causes:

  • No such file or directory — the path is not there. Either the file is missing, or a directory in the middle of the path is.
  • Permission denied — the path exists and you are not allowed to touch it that way.

That distinction is worth putting in front of a user, because the two need different fixes. "No such file" usually means a wrong path or a job that has not run yet. "Permission denied" usually means a file owned by somebody else, or a scratch area you do not have write access to — both extremely common on shared infrastructure.

Notice the first two cases give the same message for different reasons: a missing file and a missing parent directory are both "No such file or directory". So when you get it, check the whole path, not just the last component.

A good error message has three things in it:

die "cannot read $file: $!\n";

What you were trying to do ("cannot read"), which file ($file), and why ($!). Leaving out the filename is the most common omission and the most annoying one, because a script that opens six files and says only No such file or directory has told you nothing you can act on.

The trailing \n is worth knowing about: with it, die prints your message as-is; without it, Perl appends at script.pl line 42. That line number helps you and means nothing to a user, so include the \n for a message aimed at a person and leave it off while debugging.

3. $! Describes the Last Failure, Not Yours

Now the advanced point, and the reason $! belongs in the same statement as the failure.

Azvya Education Pvt. Ltd.VLSI Mentor
bang-is-fragile.pl
use strict;
use warnings;
use File::Temp qw(tempdir);

my $dir = tempdir(CLEANUP => 1);
open my $w, ">", "$dir/locked.txt" or die $!;
print $w "secret\n"; close $w;
chmod 0000, "$dir/locked.txt";

# Failure 1: the file does not exist.
open my $a, "<", "$dir/missing.txt";
my $first = "$!";

# Failure 2: a DIFFERENT problem, on a different file.
open my $b, "<", "$dir/locked.txt";
my $second = "$!";

printf "after failure 1 : %s\n", $first;
printf "after failure 2 : %s\n", $second;
printf "\$! was overwritten : %s\n", ($first eq $second ? "no" : "YES");
print  "\nso \$! describes the LAST failure, not the one you care about\n";
print  "read it in the same statement: open ... or die \"...: \$!\"\n";

chmod 0600, "$dir/locked.txt";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
after failure 1 : No such file or directory
after failure 2 : Permission denied
$! was overwritten : YES

so $! describes the LAST failure, not the one you care about
read it in the same statement: open ... or die "...: $!"

Two failures, and $! changed — from No such file or directory to Permission denied.

$! is a single global that every failing system call writes to. It holds the reason for the most recent failure anywhere in the program. So this is broken:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
open my $fh, "<", $file;
# ...a few lines of other work...
if (!$fh) { die "cannot read $file: $!" }     # $! may be about something else

Anything in between that touches the filesystem can overwrite it. The fix is to read $! in the same statement as the failure, which is exactly what the or die idiom does:

open my $fh, "<", $file or die "cannot read $file: $!\n";

There is no gap, so there is nothing to overwrite it.

For scripts where adding or die everywhere feels repetitive, the core module autodie makes the built-ins throw on failure by themselves: use autodie; and then a plain open my $fh, "<", $file; dies with a full message if it fails. It is a reasonable choice for a new script. Knowing what it replaces matters more than using it, because the scripts you inherit will not have it.

4. What To Remember

  • A failed open does not stop your program. Measured: it read 0 lines and continued, leaving the script to treat a missing file as an empty one.
  • That is the worst kind of bug — no error, and output that looks like a clean run.
  • use warnings does say readline() on closed filehandle, which is a warning on stderr, not a stop.
  • Every open gets or die.
  • $! holds the reason, and the two you will see most are No such file or directory and Permission denied — different causes needing different fixes.
  • A missing file and a missing parent directory give the same message. Check the whole path.
  • A good message has three parts: what you were doing, which file, and $!.
  • End the message with \n to suppress Perl's at line N, or leave it off while debugging.
  • $! is global and describes the LAST failure. Measured: it changed from one message to another between two opens. Read it in the same statement, or copy it immediately.
  • $! after a successful call is stale.
  • use autodie; makes the built-ins die on their own, if you want that for a new script.

Chapter 13.6 covers the operators that let you ask about a file before you open it — whether it exists, whether it is readable, how big it is — and the one case where asking first is worse than just trying.

Where this fits

Part of the Perl curriculum.