Skip to content

Copy or move a file snippet

Duplicate a file or relocate it.

Duplicate a file or relocate it. The load-bearing distinction: a move is usually a rename — an atomic, instant metadata edit — while a copy streams every byte and can die halfway, so publish-by-copy means writing to a temp name and renaming over the target. Two traps recur in every ecosystem: moving across filesystems fails with EXDEV and needs a copy-then-delete fallback (some APIs do it silently, some make you), and overwrite behavior ranges from silent clobber to hard error even within one language's options. SQL is omitted: an engine manages tables, not files on your disk — Postgres's pg_file_rename is an adminpack extension running server-side under superuser, not SQL.

Runnable recipe · 14 languages
Files & Datafilesfilesystemcopymove

Every language

14 implementations, copy-ready. One at a time with syntax highlighting, or all inline.

JSJavaScript
import { copyFile, rename, constants } from 'node:fs/promises';

// Copy — the source stays put; the destination is overwritten.
await copyFile('report.pdf', 'report-backup.pdf');

// Move — one atomic rename when both paths share a filesystem.
await rename('inbox/report.pdf', 'archive/report.pdf');

copyFile clobbers an existing destination by default — pass constants.COPYFILE_EXCL as the third argument to make it refuse instead. rename is rename(2): across filesystems (a tmpfs /tmp, another volume) it rejects with EXDEV, so fall back to copyFile + unlink.

TSTypeScript
import { copyFile, rename } from 'node:fs/promises';

const src = 'report.pdf';
const backup = 'report-backup.pdf';

await copyFile(src, backup);
await rename(src, `archive/${src}`);

Identical runtime to the JavaScript version — the bindings just pin the paths. Both promises reject on failure (ENOENT for a missing source), so wrap the pair in try/catch: without it, a failed copy can still see the source renamed away.

GoGo
package main

import (
	"io"
	"os"
)

func copyFile(src, dst string) error {
	in, err := os.Open(src)
	if err != nil {
		return err
	}
	defer in.Close()

	out, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer out.Close()

	_, err = io.Copy(out, in)
	return err
}

func main() {
	if err := copyFile("report.pdf", "report-backup.pdf"); err != nil {
		panic(err)
	}
	if err := os.Rename("inbox/report.pdf", "archive/report.pdf"); err != nil {
		panic(err)
	}
}

os.Rename is atomic and replaces the destination on Unix; across filesystems it returns a *LinkError wrapping EXDEV — then copy and os.Remove. os.Create truncates an existing destination, and io.Copy uses copy_file_range/sendfile on Linux when it can.

RsRust
use std::fs;

fn main() -> std::io::Result<()> {
    // Copy — returns the byte count; keeps the mode but not the mtime.
    fs::copy("report.pdf", "report-backup.pdf")?;

    // Move — one rename on the same filesystem.
    fs::rename("inbox/report.pdf", "archive/report.pdf")?;
    Ok(())
}

fs::rename overwrites an existing destination on Unix but fails across filesystems — io::ErrorKind::CrossesDevices since Rust 1.85 — where the fallback is fs::copy followed by fs::remove_file. fs::copy transfers permission bits only; timestamps and xattrs are lost.

PHPPHP
<?php
// Copy — overwrites an existing destination.
if (!copy('report.pdf', 'report-backup.pdf')) {
    fwrite(STDERR, "copy failed\n");
}

// Move — rename() doubles as the move; overwrites files, not directories.
if (!rename('inbox/report.pdf', 'archive/report.pdf')) {
    fwrite(STDERR, "rename failed\n");
}

Both return false with a warning instead of throwing — check the return, or set_error_handler to convert them to exceptions. rename() needs write access to both directories; on failure the source is left untouched. copy() truncates an existing destination.

PyPython
import shutil

# Copy — copy2 keeps permissions, mtime, and extended attributes.
shutil.copy2("report.pdf", "report-backup.pdf")

# Move — rename when possible, copy+delete across filesystems.
shutil.move("inbox/report.pdf", "archive/report.pdf")

shutil.move never leaves you holding EXDEV — when os.rename fails across devices it falls back to copy2 plus os.unlink. os.rename is the raw atomic primitive (same-filesystem only); copy2 overwrites an existing destination file, and shutil.copytree is the directory counterpart.

CC
#define _POSIX_C_SOURCE 200809L
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int copy_file(const char *src, const char *dst) {
    int in = open(src, O_RDONLY);
    if (in < 0) return -1;

    int out = open(dst, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (out < 0) {
        close(in);
        return -1;
    }

    char buf[65536];
    ssize_t n;
    while ((n = read(in, buf, sizeof buf)) > 0) {
        char *p = buf;
        while (n > 0) {
            ssize_t w = write(out, p, (size_t)n);
            if (w < 0) {
                close(in);
                close(out);
                return -1;
            }
            p += w;
            n -= w;
        }
    }
    if (n < 0) {
        close(in);
        close(out);
        return -1;
    }
    close(in);
    return close(out) == 0 ? 0 : -1;
}

int main(void) {
    if (copy_file("report.pdf", "report-backup.pdf") != 0) {
        perror("copy_file");
        return 1;
    }
    if (rename("inbox/report.pdf", "archive/report.pdf") != 0) {
        perror("rename");
        return 1;
    }
    return 0;
}

ISO C defines no file copy, so the portable answer is this POSIX read/write loop through a 64 KiB buffer — Linux sendfile(2) is the faster shortcut. rename(2) is atomic and overwrites an existing destination file, but fails with EXDEV across filesystems, where the fallback is copy plus unlink().

C++C++
#include <filesystem>
#include <iostream>

namespace fs = std::filesystem;

int main() {
    std::error_code ec;

    fs::copy_file("report.pdf", "report-backup.pdf",
                  fs::copy_options::overwrite_existing, ec);
    if (ec) std::cerr << "copy: " << ec.message() << '\n';

    fs::rename("inbox/report.pdf", "archive/report.pdf", ec);
    if (ec) std::cerr << "move: " << ec.message() << '\n';
}

C++17 std::filesystem. The error_code overload reports failures instead of throwing fs::filesystem_error; copy_options::skip_existing and update_existing are the alternatives to overwrite_existing. rename() maps to rename(2) on POSIX — atomic, replaces an existing regular file, but fails across volumes.

C#C#
using System.IO;

// Copy — overwrite: true replaces an existing destination; false throws.
File.Copy("report.pdf", "report-backup.pdf", overwrite: true);

// Move — an atomic rename on the same volume.
File.Move("inbox/report.pdf", "archive/report.pdf");

File.Move without the overwrite overload throws IOException when the destination exists; File.Move(src, dest, overwrite: true) landed in .NET Core 3.0. On Windows the source must not be open — a held FileStream locks it. File.Replace(src, dest, backup) is the atomic swap when you want the old file kept.

JvJava
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;

public class CopyMoveFile {
    public static void main(String[] args) throws IOException {
        Path src = Path.of("report.pdf");

        Files.copy(src, Path.of("report-backup.pdf"),
                   StandardCopyOption.REPLACE_EXISTING);

        Files.move(src, Path.of("archive/report.pdf"),
                   StandardCopyOption.REPLACE_EXISTING);
    }
}

Without REPLACE_EXISTING both throw FileAlreadyExistsException. ATOMIC_MOVE demands rename semantics — with it, all other options are ignored, and a store that cannot comply throws AtomicMoveNotSupportedException. The legacy File.renameTo reports failure as a silent false; don't use it.

SwSwift
import Foundation

let fm = FileManager.default

// Copy — throws if the destination already exists.
try fm.copyItem(atPath: "report.pdf", toPath: "report-backup.pdf")

// Move — Foundation has no overwrite flag, so clear the way first.
try? fm.removeItem(atPath: "archive/report.pdf")
try fm.moveItem(atPath: "inbox/report.pdf", toPath: "archive/report.pdf")

copyItem and moveItem refuse to clobber — there is no overwrite option, hence the try? removeItem, which tolerates a not-found file. moveItem crosses volumes fine (it copies then deletes the source), unlike raw rename(2).

KtKotlin
import java.io.File
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardCopyOption

fun main() {
    val src = File("report.pdf")

    src.copyTo(File("report-backup.pdf"), overwrite = true)
    Files.move(src.toPath(), Path.of("archive/report.pdf"), StandardCopyOption.REPLACE_EXISTING)
}

copyTo is kotlin.io's File extension over java.nio's Files.copy — without overwrite = true it throws FileAlreadyExistsException. The move goes through Files.move because File.renameTo() reports failure as a bare false with no reason attached.

RbRuby
require 'fileutils'

FileUtils.cp('report.pdf', 'report-backup.pdf')        # overwrites quietly
FileUtils.mv('inbox/report.pdf', 'archive/report.pdf') # rename, or copy+unlink

FileUtils.mv is a rename within one filesystem and degrades to copy then unlink across them; cp clobbers an existing destination without complaint. Pass preserve: true to carry over permissions and timestamps; cp_r is the recursive version.

ZigZig
const std = @import("std");

pub fn main() !void {
    const cwd = std.fs.cwd();

    // Copy — refuses to clobber an existing destination.
    try cwd.copyFile("report.pdf", cwd, "report-backup.pdf", .{});

    // Move — a rename, cheap when both paths share a filesystem.
    try cwd.rename("inbox/report.pdf", "archive/report.pdf");
}

Stdlib only. copyFile defaults to overwrite_existing = false — pass .{ .overwrite_existing = true } to replace an existing destination. rename wraps rename(2): it overwrites on POSIX but fails with error.PathAlreadyExists on Windows, and crossing filesystems returns error.RenameAcrossMountPoints.