Filehandles, files, directories

chmod#

Change the permission bits of a list of files.

chmod sets each file in LIST to the permission MODE. MODE is a numeric value — the low twelve bits are the usual Unix permission triad plus the setuid, setgid, and sticky bits, exactly as accepted by chmod(2). Both plain pathnames and open filehandles are accepted in LIST. Returns the number of files for which the underlying system call succeeded.

Synopsis#

chmod MODE, LIST

What you get back#

The number of files successfully changed, as an integer. Zero means every call failed; a value less than scalar @files means a partial failure. chmod does not throw on a per-file failure — it keeps going and tallies the successes.

$! reflects the error from the last failing file only. On a mixed success/failure run there is no built-in way to learn which files failed; if you need per-file error reporting, call chmod once per file and check the return value each time:

for my $f (@files) {
    chmod 0644, $f
        or warn "chmod $f: $!";
}

Writing MODE correctly#

MODE is an integer. The three ways to spell it:

  • Octal literal with leading zero: 0644, 0755, 0600. This is the conventional form and matches the digits you would type into chmod(1).

  • Octal literal with 0o prefix: 0o644, 0o755. Accepted since Perl 5.34; equivalent to the leading-zero form and easier to read as “octal” at a glance.

  • Symbolic constants from Fcntl: use Fcntl qw(:mode); S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH evaluates to 0755. Useful when a permission set is built up from pieces.

A string argument is almost always a bug:

chmod "0644", "foo";     # WRONG — stringifies as decimal 644 (octal 1204),
                         #         grants mode --w----r-T
chmod 644,    "foo";     # WRONG — decimal 644 is octal 1204, same result
chmod 0644,   "foo";     # right
chmod 0o644,  "foo";     # right (Perl 5.34+)

If the mode genuinely arrives as a string (configuration file, CLI argument), convert it with oct first:

my $mode = "0644";       # string from config
chmod oct($mode), "foo"; # oct("0644") == 0644

Global state it touches#

Sets $! on any failure to match the last failing chmod(2) / fchmod(2) call. Reads no interpreter globals.

Examples#

Change one file, check for success:

chmod 0755, "install.sh"
    or die "chmod install.sh: $!";

Change many files in a single call, capture the count:

my $cnt = chmod 0644, "a.txt", "b.txt", "c.txt";
print "updated $cnt of 3 files\n";

With an array of pathnames:

my @executables = glob "bin/*";
chmod 0755, @executables;

Preserve existing bits, add owner write. Reads the mode with stat, masks to the permission bits, ORs in the new bit, and writes it back:

open my $fh, "<", "foo" or die $!;
my $perm = (stat $fh)[2] & 07777;
chmod $perm | 0200, $fh;

Symbolic form via Fcntl:

use Fcntl qw(:mode);
chmod S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH, @executables;
# identical to: chmod 0755, @executables;

Per-file error reporting (needed to learn which file failed on a multi-file call):

my @failed;
for my $f (@files) {
    push @failed, $f unless chmod 0644, $f;
}
warn "chmod failed on: @failed\n" if @failed;

Edge cases#

  • Empty list: chmod 0644 returns 0. The mode is evaluated but no system call is made.

  • String modes: chmod "0644", $f and chmod "+x", $f are both wrong. chmod never parses symbolic mode strings — unlike chmod(1), the built-in takes a number, not a specification.

  • Filehandles vs barewords: a bareword in LIST is a filename, not a filehandle. Pass a filehandle as a glob or glob reference:

    chmod 0644, *FH;         # filehandle
    chmod 0644, \*FH;        # filehandle
    chmod 0644, $fh;         # filehandle (lexical)
    chmod 0644, "FH";        # filename "FH" — almost certainly a bug
    

    On systems without fchmod(2), passing a filehandle raises an exception. Linux supports fchmod(2), so on the platforms pperl targets this always works.

  • Setuid / setgid / sticky bits: the high three bits of the mode word (04000, 02000, 01000) set setuid, setgid, and the sticky bit respectively. 0o4755 makes a setuid executable; 0o2775 a setgid directory.

  • Symlinks: chmod follows symlinks. Changing permissions on the symlink itself is not portable and is not exposed by this built-in — use lchmod from a module if the platform supports it.

  • Permission to change permission: only the file’s owner (and root) may call chmod(2) on a file. Failure sets $! to EPERM.

  • Non-existent paths: chmod on a missing path sets $! to ENOENT and contributes 0 to the success count. It does not warn and does not die.

  • umask does not apply: umask masks the mode of newly created files from open, sysopen, mkdir, and similar. chmod sets the mode literally — no masking.

Differences from upstream#

Fully compatible with upstream Perl 5.42.

See also#

  • chown — the sibling call for changing owner/group; same list-and-return-count shape

  • stat — read the current mode so you can modify it rather than replace it wholesale

  • umask — default permission mask applied to new files; unrelated to chmod, which sets bits literally

  • open / sysopen — create files; the mode argument to sysopen is masked by umask at creation time and can then be tightened or relaxed with chmod

  • oct — parse "0644" into the integer 0644 when the mode arrives as a string

  • $! — system error from the last failing file; only meaningful for single-file calls or immediately after a failing per-file call in a loop