Filehandles, files, directories

rename#

Change the name of a file.

rename asks the operating system to rebind the path OLDNAME to NEWNAME. On a POSIX filesystem this is a single atomic directory operation: after the call returns successfully, NEWNAME refers to the file that used to be reachable as OLDNAME, and OLDNAME no longer exists. It is the same primitive the shell mv command reaches for when source and destination live on the same filesystem.

Synopsis#

rename OLDNAME, NEWNAME

What you get back#

1 on success, 0 on failure with $! set to the errno the underlying rename(2) syscall returned. Always check the return value — a silent failure here leaves the filesystem in a state the rest of the program does not expect:

rename $tmp, $final
    or die "rename $tmp -> $final failed: $!";

Atomicity and the same-filesystem rule#

rename is atomic only when OLDNAME and NEWNAME resolve to the same filesystem. Within that filesystem, no intermediate state is ever visible: another process reading the directory sees either the old name or the new name, never both and never neither. This is why the canonical “write a file safely” idiom writes to a temporary file in the same directory and then renames it into place:

open my $fh, ">", "$path.tmp" or die "open: $!";
print $fh $data;
close $fh or die "close: $!";
rename "$path.tmp", $path
    or die "rename: $!";

Across filesystems the call fails with EXDEV ("Invalid cross-device link") and nothing is moved. The system mv command papers over this by falling back to copy-then-unlink; rename does not. For a portable cross-device move, use move from File::Copy, which performs the copy-and-unlink fallback itself.

Examples#

Rename within a directory:

rename "draft.txt", "final.txt"
    or die "rename: $!";

Atomic file replacement — write to a sibling tempfile, then rename:

my $tmp = "$path.$$.tmp";
open my $fh, ">", $tmp or die "open $tmp: $!";
print $fh $content;
close  $fh             or die "close $tmp: $!";
rename $tmp, $path     or die "rename $tmp -> $path: $!";

Detect the cross-device case and fall back to File::Copy::move:

use Errno qw(EXDEV);
use File::Copy qw(move);

unless (rename $src, $dst) {
    if ($! == EXDEV) {
        move($src, $dst) or die "move: $!";
    }
    else {
        die "rename: $!";
    }
}

Rename every .log in a directory to .log.old:

for my $f (glob "*.log") {
    rename $f, "$f.old"
        or warn "rename $f: $!";
}

Edge cases#

  • NEWNAME already exists: silently clobbered. rename does not ask, warn, or refuse. If the target is a regular file it is unlinked as part of the operation; if it is a directory the call fails with ENOTDIR or EISDIR depending on whether OLDNAME is a directory. To get “refuse to overwrite” semantics, test with -e first (and accept that the check is racy — there is no portable renameat2(RENAME_NOREPLACE) exposed through rename itself).

  • Cross-filesystem (EXDEV): fails, leaves both paths intact. Use File::Copy::move when the destination may be on another mount.

  • Open filehandles: on Linux (and POSIX generally) renaming a file that is currently open is fine. The open filehandle keeps pointing at the same inode; subsequent reads and writes through it continue to work. New opens use the new name.

  • Directories: renaming a directory is allowed, but only when NEWNAME does not exist or is an empty directory. Moving a directory into a subdirectory of itself fails with EINVAL.

  • Permissions: rename needs write + execute permission on both the source and destination parent directories, not on the file itself. Failure shows up as EACCES or EPERM.

  • Sticky-bit directories (/tmp): if the destination parent has the sticky bit set, you must own either the source file or the destination parent; otherwise the call fails with EPERM even when the directory is world-writable.

  • Symlinks: rename operates on the link itself, never on its target. rename "link", "other" renames the symlink; the file it points at is untouched.

  • Trailing slash on OLDNAME: many kernels reject this with ENOTDIR when the source is not a directory. Do not rely on trailing-slash behaviour being portable.

  • Same-path rename: rename $p, $p succeeds and does nothing (POSIX-required behaviour) as long as $p exists.

Differences from upstream#

Fully compatible with upstream Perl 5.42.

See also#

  • unlink — remove a file by name; the tear-down counterpart to creating one with rename into place

  • link — make an additional hard-link name for an existing file without removing the original

  • symlink — create a symbolic link; note that rename on a symlink moves the link, not its target

  • File::Copy — portable move and copy that handle the cross-filesystem (EXDEV) case rename cannot

  • $!errno after a failed rename; compare against constants from Errno (EXDEV, EACCES, ENOENT, …)