Skip to content
VLSI Mentor

Perl Part I · Module 13 · File Handling

File Test Operators

Perl has a set of one-letter operators that answer simple questions about a path without opening it. Does it exist, is it a plain file, is it a directory, how many bytes is it. They are easy to learn and worth having, and they are also the subject of the most common well-intentioned mistake in file handling: checking that a file exists and then opening it. Section three deletes the file in between those two steps and shows the open failing anyway. On your laptop that looks like a contrived example. On a shared filesystem with cleanup jobs and other people's tools running, it is Tuesday. The chapter covers the useful operators first, then the one whose return value needs care, then when not to use any of them.

Foundation10 min readPerlPart IFile Handling

Part I · Module 13 · Chapter 13.6 · File Test Operators

1. The Tests You Will Use

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

my $dir = tempdir(CLEANUP => 1);
open my $w, ">", "$dir/report.txt" or die $!;
print $w "u_alu/A -0.12\n"; close $w;
open my $e, ">", "$dir/empty.txt" or die $!; close $e;
mkdir "$dir/sub" or die $!;

my @paths = ("$dir/report.txt", "$dir/empty.txt", "$dir/sub", "$dir/missing.txt");
my @names = qw(report.txt empty.txt sub/ missing.txt);

printf "%-13s %-4s %-4s %-4s %-4s %s\n", "PATH", "-e", "-f", "-d", "-s", "-z";
for my $i (0 .. $#paths) {
    my $p = $paths[$i];
    printf "%-13s %-4s %-4s %-4s %-4s %s\n", $names[$i],
        (-e $p ? "y" : "-"),
        (-f $p ? "y" : "-"),
        (-d $p ? "y" : "-"),
        (defined(-s $p) && -s $p ? (-s $p) : "0"),
        (-z $p ? "y" : "-");
}
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
PATH          -e   -f   -d   -s   -z
report.txt    y    y    -    14   -
empty.txt     y    y    -    0    y
sub/          y    -    y    64   -
missing.txt   -    -    -    0    -

Five operators, four paths, and the table is the definition:

  • -e — exists. True for anything: files, directories, links.
  • -f — a plain file. Note it is false for the directory, which is what makes it the right test when you mean "a file I can read".
  • -d — a directory.
  • -s — size in bytes. See the next section.
  • -z — zero size. True only for empty.txt.

Three more worth knowing: -r, -w and -x ask whether you can read, write or execute the path. They are about your permissions, not the file's bits, so they already account for who you are running as.

One detail from that table that catches people: -s on the directory returned 64. A directory is a thing on disk and has a size. So -s is only a meaningful "how much data is in here" answer for a plain file — combine it with -f if the path could be either.

The operators work on a filehandle too, not just a path: -s $fh after opening. And they all use whatever path is in $_ if you give them nothing, which is why my @big = grep { -s > 1000 } @files; reads the way it does.

2. -s Has Three Answers

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

my $dir = tempdir(CLEANUP => 1);
open my $e, ">", "$dir/empty.txt" or die $!; close $e;
open my $f, ">", "$dir/full.txt"  or die $!; print $f "data\n"; close $f;

for my $n (qw(empty.txt full.txt missing.txt)) {
    my $p = "$dir/$n";
    my $size = -s $p;
    printf "%-12s -e:%-3s -s returns:%-8s useful as a truth test: %s\n",
        $n,
        (-e $p ? "yes" : "no"),
        (defined $size ? "[$size]" : "undef"),
        ($size ? "true" : "FALSE");
}
print "\n-s returns the SIZE, so an empty file is falsy and a missing one is undef\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
empty.txt    -e:yes -s returns:[0]      useful as a truth test: FALSE
full.txt     -e:yes -s returns:[5]      useful as a truth test: true
missing.txt  -e:no  -s returns:undef    useful as a truth test: FALSE

-s returns the SIZE, so an empty file is falsy and a missing one is undef

-s does not answer yes or no. It returns a number, and that gives three different outcomes:

  • A file with data: the byte count, which is true.
  • An empty file: 0, which is false — but the file exists.
  • A missing file: undef, which is also false.

This is the same shape as Chapter 11.10's warning about captured zeros: a function that returns a number has a false value in its normal range, so truth-testing it conflates "zero" with "nothing".

3. Do Not Test Then Open

Now the advanced point, which is about when not to use any of this.

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

my $dir = tempdir(CLEANUP => 1);
my $file = "$dir/vanishing.txt";
open my $w, ">", $file or die $!; print $w "here\n"; close $w;

# The "look before you leap" shape.
if (-e $file) {
    # Something else deletes the file right here. In a real flow that is
    # another job, a cleanup script, or a tool finishing up.
    unlink $file;

    my $ok = open my $fh, "<", $file;
    printf "-e said yes, then open : %s\n", ($ok ? "worked" : "FAILED: $!");
}

# The "just try it" shape handles the same situation with one test.
open my $w2, ">", $file or die $!; print $w2 "here\n"; close $w2;
unlink $file;
if (open my $fh, "<", $file) { print "second shape: opened\n" }
else                          { printf "second shape: %s -- one test, correct answer\n", $! }
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
-e said yes, then open : FAILED: No such file or directory
second shape: No such file or directory -- one test, correct answer

-e returned true. The open on the very next lines failed.

The script deletes the file in between to make the timing visible, and that is the only artificial part. The situation is ordinary: between your test and your open, another job can delete the file, a cleanup script can sweep the scratch area, or a tool can replace it. Nothing you can write makes those two steps happen together.

This pattern has a name — a time-of-check to time-of-use race — and the fix is to stop asking twice:

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

That single statement checks and acts at the same moment. If the file is missing you find out, with a reason, from the operation you actually wanted to perform. The second half of the measurement shows it: one test, correct answer, and a useful message.

So when are the test operators the right tool? When the answer is the thing you want, not a prelude to opening:

  • Choosing among paths: my @reports = grep { -f && -s } @candidates;
  • Reporting on files without reading them — a size summary, a listing.
  • -d to decide whether to recurse, which Chapter 13.9 uses.
  • Checking a directory is writable before a long job, as an early sanity check — accepting that it is advisory, not a guarantee.

4. What To Remember

  • -e exists, -f plain file, -d directory, -z zero size. -f is false for a directory, which is why it is the right "can I read this" test.
  • -r, -w, -x ask about your permissions, so they already account for who you are.
  • -s returns a size, and a directory has one — measured as 64 here. Pair it with -f.
  • -s has three outcomes: a byte count, 0 for an empty file, undef for a missing one. The last two are both false.
  • if (-s $file) cannot tell "empty" from "missing" — and those usually mean different things. Test -e and -s separately when it matters.
  • The tests work on a filehandle and default to $_, which is what makes grep { -s > 1000 } read well.
  • Do not test then open. Measured: -e said yes and the next open failed, because the file was deleted in between.
  • open ... or die checks and acts in one step, and gives you the reason.
  • Use the tests when the answer is the goal — picking files, reporting, deciding whether to recurse.
  • Checking -e before writing has the same race, with a worse outcome. Use Chapter 13.13's atomic write.

Chapter 13.7 covers the shortcut that makes a Perl script behave like a standard command-line tool: the diamond operator with no filehandle, which reads the files named on the command line, or standard input when there are none.

Where this fits

Part of the Perl curriculum.