ZetCode

Perl file tests

last modified October 20, 2026

File test operators are unary operators that examine a file or a directory. Each one is a dash and a single letter, such as -e or -d, followed by a path or a filehandle. They return true or false, or a value such as the size or the age of the file. File tests are the quick way to check a path before opening it.

Existence and type: -e, -f, -d, -l

The test -e checks that a path exists, -f that it is a plain file, -d that it is a directory and -l that it is a symbolic link.

exists.pl
#!/usr/bin/perl

use strict;
use warnings;
use v5.34.0;
use File::Temp qw(tempdir);

my $dir = tempdir(CLEANUP => 1);
my $file = "$dir/data.txt";
my $link = "$dir/data.lnk";

open(my $fh, '>', $file) or die "Cannot open $file: $!";
print $fh "hello\n";
close $fh;

symlink($file, $link) or die "Cannot link: $!";

for my $path ($file, $link, $dir, "$dir/missing") {
    my $name = $path =~ s/^\Q$dir\E\/?//r || 'tempdir';
    say sprintf "%-9s -e:%d -f:%d -d:%d -l:%d", $name,
        (-e $path ? 1 : 0), (-f $path ? 1 : 0),
        (-d $path ? 1 : 0), (-l $path ? 1 : 0);
}

The script creates a file, a symbolic link to it, and looks at the file, the link, the directory and a missing path. The tests that follow symbolic links (-e, -f, -d) report on the target, so the link counts as a plain file. Only -l looks at the link itself. A missing path fails every test. The temporary directory is removed when the program ends.

$ perl exists.pl
data.txt  -e:1 -f:1 -d:0 -l:0
data.lnk  -e:1 -f:1 -d:0 -l:1
tempdir   -e:1 -f:0 -d:1 -l:0
missing   -e:0 -f:0 -d:0 -l:0

File size: -s and -z

The test -s returns the size of a file in bytes and is false when the file is empty. The test -z is true when the file has zero size.

size.pl
#!/usr/bin/perl

use strict;
use warnings;
use v5.34.0;
use File::Temp qw(tempdir);

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

sub create {
    my ($name, $content) = @_;
    open(my $fh, '>', "$dir/$name") or die $!;
    print $fh $content;
    close $fh;
    return "$dir/$name";
}

my $empty = create('empty.txt', '');
my $full  = create('full.txt', "0123456789\n");

for my $path ($empty, $full) {
    my $size = -s $path;
    say sprintf "%-10s zero: %s, size: %s", (split m{/}, $path)[-1],
        (-z $path ? 'yes' : 'no'),
        $size // 'empty';
}

The create helper writes a file and returns its path. -s returns the byte count, 11 for the text with its newline, so it can be used both as a condition and as a value. For the empty file -z is true. Both tests return undef for a path that does not exist, which makes -z unsuitable as a check for existence.

$ perl size.pl
empty.txt  zero: yes, size: 0
full.txt   zero: no, size: 11

Permissions: -r, -w, -x

The tests -r, -w and -x tell whether the effective user can read, write or execute the file. Their uppercase variants -R, -W and -X use the real user id instead.

perms.pl
#!/usr/bin/perl

use strict;
use warnings;
use v5.34.0;
use File::Temp qw(tempdir);

my $dir = tempdir(CLEANUP => 1);
my $file = "$dir/script.sh";

open(my $fh, '>', $file) or die $!;
print $fh "#!/bin/sh\necho hi\n";
close $fh;

for my $mode (0644, 0444, 0755) {
    chmod $mode, $file;
    say sprintf "%04o  readable: %s  writable: %s  executable: %s", $mode,
        (-r $file ? 'yes' : 'no'),
        (-w $file ? 'yes' : 'no'),
        (-x $file ? 'yes' : 'no');
}

The loop changes the mode with chmod and repeats the tests. With mode 0444 the file is no longer writable and with 0755 it becomes executable. The result depends on the user who runs the script: the superuser can read and write any file, so the outcome shown here is for a regular user.

$ perl perms.pl
0644  readable: yes  writable: yes  executable: no
0444  readable: yes  writable: no  executable: no
0755  readable: yes  writable: yes  executable: yes

File age: -M, -A, -C

The tests -M, -A and -C return the age of the file in days, relative to the time the program started ($^T). They report the modification time, the access time and the inode change time.

age.pl
#!/usr/bin/perl

use strict;
use warnings;
use v5.34.0;
use File::Temp qw(tempdir);

my $dir = tempdir(CLEANUP => 1);
my $file = "$dir/old.log";

open(my $fh, '>', $file) or die $!;
close $fh;

my $two_days_ago = time - 2 * 24 * 3600;
utime($two_days_ago, $two_days_ago, $file) or die $!;

say sprintf "modified %.1f days ago", -M $file;
say sprintf "accessed %.1f days ago", -A $file;

if (-M $file > 1) {
    say "file is older than one day";
}

The utime function sets the access and modification times to two days ago, so both tests return about 2. The results are fractional numbers, which is why the example formats them with sprintf. The age is measured from the start of the program, not from the current moment, so a long-running script should set $^T if it needs fresh values. Files modified in the future have a negative age.

$ perl age.pl
modified 2.0 days ago
accessed 2.0 days ago
file is older than one day

Stacked tests and the underscore filehandle

Since Perl 5.10 file tests can be stacked: -f -r $file is equivalent to -r $file && -f $file. Each test looks at the file, so Perl normally calls stat repeatedly. The special filehandle _ reuses the result of the last test or stat.

stacked.pl
#!/usr/bin/perl

use strict;
use warnings;
use v5.34.0;
use File::Temp qw(tempdir);

my $dir = tempdir(CLEANUP => 1);
my $file = "$dir/report.txt";

open(my $fh, '>', $file) or die $!;
print $fh "some text\n";
close $fh;
chmod 0644, $file;

say "regular and readable"        if -f -r $file;
say "regular and executable"      if -f -x $file;
say "regular, readable, writable" if -f -r -w $file;

if (-e $file) {
    say "size: ", -s _, " bytes, mode: ", sprintf("%04o", (stat _)[2] & 07777);
    say "is a file: ", (-f _ ? 'yes' : 'no');
}

The three stacked checks show that the file is regular, readable and writable, but not executable. After -e $file has run, the later tests use _ instead of the path. This saves system calls and avoids a race where the file changes between two tests. The stacked form is evaluated right to left, so the test nearest the path runs first.

$ perl stacked.pl
regular and readable
regular, readable, writable
size: 10 bytes, mode: 0644
is a file: yes

Text and binary files, filehandles: -T, -B, -t

The tests -T and -B guess whether a file is text or binary by examining its first block. File tests also accept an open filehandle, and -t tells whether a handle is connected to a terminal.

text.pl
#!/usr/bin/perl

use strict;
use warnings;
use v5.34.0;
use File::Temp qw(tempdir);

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

open(my $t, '>', "$dir/text.txt") or die $!;
print $t "plain text\nsecond line\n";
close $t;

open(my $b, '>:raw', "$dir/data.bin") or die $!;
print $b pack('C*', 0, 1, 2, 255, 254, 0, 128, 7);
close $b;

for my $name (qw(text.txt data.bin)) {
    my $path = "$dir/$name";
    say sprintf "%-9s text: %s  binary: %s", $name,
        (-T $path ? 'yes' : 'no'), (-B $path ? 'yes' : 'no');
}

open(my $in, '<', "$dir/text.txt") or die $!;
say "open handle is a file: ", (-f $in ? 'yes' : 'no');
say "handle size: ", -s $in;
close $in;

say "STDIN is a terminal: ", (-t STDIN ? 'yes' : 'no');

The text file passes -T, while the file with null bytes and high bytes passes -B. The test is a heuristic, so an empty file is both text and binary. Applying -f and -s to a lexical filehandle works the same as with a path. Here the script was run with its input redirected, so -t STDIN is false; it is true when run interactively.

$ perl text.pl
text.txt  text: yes  binary: no
data.bin  text: no  binary: yes
open handle is a file: yes
handle size: 23
STDIN is a terminal: no

Best Practices

In this article we have covered the Perl file test operators: existence and type, size, permissions, age, stacked tests, the _ filehandle, and the text and binary checks.

Source

perlfunc: -X

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Perl tutorials.