cp Command in Linux: Complete Guide to Copying Files, Directories, and Parameters

cp command in Linux and it perimeters

cp command in Linux and it perimeters

Introduction

cp seems like the simplest possible operation — duplicate a file — but I’ve been burned enough times by its default behaviors (silently overwriting a destination, not preserving permissions the way I expected, not being recursive by default) that I’ve learned to be deliberate with it. This guide covers the syntax, what actually happens on disk during a copy, and the flags I reach for constantly in real work.

What Is the cp Command?

cp copies files and directories. It’s a GNU coreutils utility:

$ cp --version
cp (GNU coreutils) 9.4
Copyright (C) 2023 Free Software Foundation, Inc.

Basic Syntax

cp [OPTION]... SOURCE DEST
cp [OPTION]... SOURCE... DIRECTORY
cp [OPTION]... -t DIRECTORY SOURCE...

How cp Works Internally

Unlike mv on the same filesystem, cp always reads the actual bytes of the source and writes them to a new location — it allocates a brand-new inode for the destination and copies the data blocks. This is true even when source and destination are on the same filesystem, which is the fundamental difference from mv. I verified this directly:

$ echo "hello" > src.txt
$ cp src.txt dst.txt
$ cat dst.txt
hello
$ ls -li src.txt dst.txt
573462 -rw-r--r-- 1 root root 6 Jul 31 01:36 src.txt
573465 -rw-r--r-- 1 root root 6 Jul 31 01:36 dst.txt

Different inode numbers (573462 vs 573465) confirm dst.txt is a completely independent file with its own copy of the data — editing one has zero effect on the other, unlike a hard link.

By default, cp does not preserve every attribute of the source. Ownership typically becomes whoever ran the copy (not the original owner) unless you’re root or use -p/--preserve, and timestamps update to the copy time rather than the original modification time unless preservation is requested. This surprises people coming from mv, where ownership and timestamps carry over automatically because the inode itself is reused.

Full List of Parameters

OptionLong formDescription
-a--archiveArchive mode: equivalent to -dR --preserve=all, ideal for full, faithful copies
-bMake a backup of each existing destination file
--backup[=CONTROL]Same, with backup method control
-dSame as --no-dereference --preserve=links
-f--forceRemove existing destination files and retry (if copy fails)
-i--interactivePrompt before overwrite
-l--linkHard link files instead of copying
-L--dereferenceAlways follow symbolic links in SOURCE
-n--no-clobberDo not overwrite an existing file
-pSame as --preserve=mode,ownership,timestamps
--preserve[=ATTR_LIST]Preserve specified attributes: mode, ownership, timestamps, context, links, xattr, or all
-r, -R--recursiveCopy directories recursively
--reflink[=WHEN]Use copy-on-write reflinks where supported (auto, always, never)
-s--symbolic-linkMake symbolic links instead of copying
-u--updateCopy only when SOURCE is newer than destination, or destination is missing
-v--verboseExplain what is being done
-x--one-file-systemStay on this filesystem when copying recursively
--helpDisplay help and exit
--versionOutput version information and exit

Practical Examples with Output

Simple copy:

$ cp src.txt dst.txt
$ cat dst.txt
hello

Copying a directory tree recursively:

$ mkdir -p a/b/c
$ cp -r a a_copy
$ find a_copy
a_copy
a_copy/b
a_copy/b/c

I confirmed this directly — the entire nested structure was reproduced under the new name.

Archive-mode copy, preserving permissions, ownership, timestamps, and symlinks — the gold standard for a faithful backup copy:

$ cp -a /etc/myapp /backup/myapp_$(date +%F)

Preventing accidental overwrite:

$ echo "important" > report.txt
$ echo "junk" > /tmp/report.txt
$ cp -n /tmp/report.txt report.txt
$ cat report.txt
important

Nothing was overwritten.

Interactive confirmation:

$ cp -i /tmp/report.txt report.txt
cp: overwrite 'report.txt'? y

Copying only newer files (useful for lightweight sync-style updates):

$ cp -u source/*.conf /etc/myapp/

Using a copy-on-write reflink on filesystems that support it (Btrfs, XFS with reflink support) — near-instant “copy” that shares data blocks until either file is modified:

$ cp --reflink=auto largefile.iso largefile_copy.iso

Common Use Cases

Shell Scripting and Automation

A safe “backup before edit” pattern I use constantly:

#!/usr/bin/env bash
set -euo pipefail

CONF="/etc/myapp/config.yml"
cp -a -- "$CONF" "${CONF}.bak.$(date +%Y%m%d_%H%M%S)"

Copying a whole directory tree into a deployment target while excluding certain files (using rsync since plain cp has no exclude mechanism):

rsync -a --exclude='.git' --exclude='node_modules' src/ /var/www/app/

I mention rsync deliberately here because cp -r has no built-in filtering — for anything beyond a full, unfiltered directory copy, rsync is usually the better tool even though cp can technically handle simple recursive copies.

Real-World System Administration Workflows

Comparing cp to Related Commands

Troubleshooting Common cp Issues

“cp: omitting directory” when copying a directory: you forgot -r/-R — cp requires an explicit recursive flag for directories, matching rm‘s protective behavior.

Copied file has wrong owner: cp by default sets ownership to the user running the copy, not the source’s owner, unless you’re root or pass -p/--preserve=ownership (as an unprivileged user, -p‘s ownership preservation only works if you already own the target UID/GID, otherwise the kernel silently keeps the copying user as owner).

Copy overwrote something unintentionally: cp overwrites the destination silently by default; add -i interactively or -n in scripts where that matters.

“cp: cannot create regular file: No space left on device” mid-copy: check df -h on the destination; also check df -i for exhausted inodes, since that failure mode gives a similar error.

Performance Considerations

Full data copies are inherently I/O-bound and proportional to file size — there’s no shortcut for a true byte-for-byte duplicate on a traditional filesystem. On copy-on-write filesystems (Btrfs, and XFS with reflink support), --reflink=auto gives you an effectively instant “copy” that only starts consuming real extra disk space once one of the two files is actually modified (a copy-on-write fork at the block level) — worth using whenever you’re duplicating large files on a filesystem that supports it. For copying many small files, cp -r can be slower than tar-based or rsync-based approaches due to per-file syscall overhead; benchmark on your actual workload if performance at scale matters.

Security Implications

Default cp behavior resets ownership to the copying user and doesn’t preserve special permission bits like setuid/setgid unless explicitly asked via -p/--preserve=mode. This is usually a good thing security-wise — copying a setuid binary without meaning to keep its setuid bit set is generally the safer default. Conversely, when you deliberately need a faithful copy of a system file including its exact permissions (for backup or migration), -a/--preserve=all is essential, and you should verify the result with ls -l afterward rather than assuming it matched.

Best Practices

Compatibility Across Distributions

GNU cp is consistent across Debian/Ubuntu, RHEL/Fedora, Arch, and openSUSE. --reflink support depends on the underlying filesystem (Btrfs and reflink-enabled XFS support it; ext4 does not), not the distribution itself — check with cp --reflink=always and watch for a fallback error if unsupported. BusyBox cp (Alpine, minimal containers) supports -r, -p, -a, and -f but has a reduced --preserve attribute list — verify with cp --help before relying on fine-grained preservation flags there.

Advanced Scenarios I’ve Run Into

Copying with sparse file awareness: some files (like disk images or database files with large gaps of zero bytes) are stored as “sparse” — the filesystem doesn’t actually allocate blocks for long runs of zeros. A naive copy can turn a sparse file into a fully-allocated one, ballooning disk usage. GNU cp since coreutils 8.x detects sparse regions automatically in most cases and preserves them, but you can be explicit with --sparse=always to force this behavior, or --sparse=never to force full allocation:

$ cp --sparse=always disk.img disk_copy.img

Copying while preserving extended attributes and ACLs, which -p alone does not cover — you need to explicitly include xattr and rely on -a (which implies --preserve=all including xattr where supported):

$ cp -a --preserve=all secured_file.txt secured_copy.txt

Copying only specific file types out of a mixed directory, combining find with cp since cp itself has no filtering logic:

$ find /var/log/myapp -name "*.log" -exec cp -t /backup/logs/ {} +

Verifying a copy actually matches the source — cp doesn’t checksum anything by default, so for anything where data integrity genuinely matters (large backups, critical config files), I follow up with an explicit comparison:

$ cp important_data.bin /backup/
$ cmp important_data.bin /backup/important_data.bin && echo "Copy verified identical"
Copy verified identical

cmp byte-for-byte compares two files and exits silently with status 0 if they’re identical, or reports the first differing byte if not — a cheap sanity check worth building into any script where a copy failure would be costly.

Copying Over SSH and Between Hosts

Plain cp only ever operates on the local filesystem (or filesystems the local kernel can already see, including network mounts like NFS). For copying between separate machines without a shared mount, the natural extensions are scp (which shares much of cp‘s flag philosophy but transfers over SSH) or rsync -e ssh, which additionally supports resuming interrupted transfers and only sending the parts of a file that actually changed on repeat runs — a meaningful advantage over a fresh cp-style full retransfer every time.

Summary

The core thing to internalize about cp is that, unlike mv, it always physically duplicates data (except on copy-on-write filesystems with --reflink), and it does not preserve ownership or timestamps by default the way a same-filesystem mv automatically does. Once that’s clear, the flags fall into place naturally: -r for directories, -a for faithful full-attribute copies, -i/-n for safety against overwrites, and --reflink=auto when you want speed on a filesystem that supports it.

References

Exit mobile version