Copying Files and Directories (cp)
The cp command copies files and directories from one location to another on a Linux system. It is one of the most frequently used commands at the shell — duplicating a config file before you edit it, backing up a project directory, or placing copies of files onto a USB drive. Understanding exactly how cp decides where files land, and which flags control overwriting, recursion, and preserved metadata, will save you from some of the most common and occasionally destructive command-line mistakes.
Overview: How cp Works
cp does not move or link a file — it reads the bytes of the source file and writes a brand-new copy at the destination. The source and destination end up as two completely independent files, each with their own inode (the kernel data structure that tracks a file’s metadata: size, permission bits, owner, timestamps, and the disk blocks holding its data). Because they are separate inodes, editing one afterward has no effect on the other. This is different from a hard link, which points two names at the very same inode.
When you run cp source dest, the shell hands cp two arguments, and cp decides what to do based on what dest currently is:
- If
destdoes not exist,cpcreates a new file with that exact name and copies the source’s contents into it. - If
destexists and is a regular file,cpoverwrites its contents — silently, by default, with no warning. - If
destexists and is a directory,cpcopies the source inside that directory, keeping the source’s original filename.
By default, cp refuses to copy a directory at all — it prints an error rather than silently skipping it, because copying an entire directory tree is a fundamentally different (and potentially much larger) operation than copying one file. You must explicitly pass -r (or -R) to tell cp to recurse into subdirectories, creating each directory it finds at the destination and copying every file inside.
Metadata handling is a frequent source of confusion. By default, cp copies the permission bits (the rwx mode) of the source file, but it does not preserve the original owner or the original modification timestamp — the new file is owned by whoever ran the command, and its timestamp is set to the moment the copy happened. If you need an exact clone, including ownership, timestamps, and symbolic links, use -p (preserve mode/ownership/timestamps) or the more thorough -a (archive mode), covered below.
Syntax
cp [OPTIONS] SOURCE DESTINATION
cp [OPTIONS] SOURCE... DIRECTORY
The second form is used when copying multiple files at once: every source listed before the final argument is copied into that final argument, which must already exist as a directory.
| Flag | Meaning |
|---|---|
-r, -R |
Copy directories recursively; required to copy a directory at all |
-i |
Interactive: ask for confirmation before overwriting an existing destination file |
-n |
Never overwrite an existing file; silently skip it instead of prompting |
-f |
Force: if a destination file cannot be written to, remove it first and try again without prompting |
-u |
Update: only copy when the source is newer than the destination, or the destination is missing |
-v |
Verbose: print each file as it’s copied |
-p |
Preserve the source’s mode, ownership, and timestamps on the copy |
-a |
Archive: recursive copy that preserves as much as possible — mode, ownership, timestamps, and symbolic links — ideal for backups |
Examples
Example 1: Copying a single file
cp ~/documents/report.txt ~/documents/report_backup.txt
Verify it landed correctly:
ls -l ~/documents/report_backup.txt
Output:
-rw-r--r-- 1 alice alice 4096 Aug 4 10:15 /home/alice/documents/report_backup.txt
cp printed nothing on success — silence means it worked. A new, independent file report_backup.txt now exists with the same content and the same permission bits as report.txt, but its timestamp reflects when the copy was made, not when the original was last edited.
Example 2: Copying a directory tree
cp -r ~/projects/website /var/backups/website-2026-08-04
ls /var/backups/website-2026-08-04
Output:
about.html css images index.html js
Because the destination /var/backups/website-2026-08-04 did not already exist, cp -r created it and copied the entire contents of ~/projects/website underneath it — every subdirectory and file, recursively. Notice the destination directory’s basename here is not the same as the source’s; if the destination had instead been an existing directory, the source folder itself (as website/) would have been nested inside it.
Example 3: Copying multiple files into a directory, verbosely
cp -v -p invoice_jan.pdf invoice_feb.pdf invoice_mar.pdf ~/archive/invoices/
Output:
'invoice_jan.pdf' -> '/home/alice/archive/invoices/invoice_jan.pdf'
'invoice_feb.pdf' -> '/home/alice/archive/invoices/invoice_feb.pdf'
'invoice_mar.pdf' -> '/home/alice/archive/invoices/invoice_mar.pdf'
All three PDFs are copied into the ~/archive/invoices/ directory, keeping their original filenames. The -v flag makes cp announce each copy as it happens (useful when copying many files, so you can confirm nothing silently failed), and -p preserves each file’s original modification timestamp and ownership on the copy — handy for archives where you want to know when the invoice was actually created, not when it was archived.
Example 4: A full backup with archive mode
cp -a ~/projects/website /media/usb/website-backup
The -a (archive) flag is the one to reach for when you want a true clone of a directory tree — recursion, permissions, ownership, timestamps, and symbolic links (copied as links, not followed and duplicated) are all preserved. This is why -a is the standard choice for backup scripts, over a plain -r.
How It Works Step by Step
Consider a small backup script that wraps cp -r and checks whether it actually succeeded:
#!/usr/bin/env bash
SOURCE_DIR="$HOME/projects/website"
BACKUP_DIR="/var/backups/website-$(date +%Y-%m-%d)"
cp -r "$SOURCE_DIR" "$BACKUP_DIR"
if [ $? -eq 0 ]; then
echo "Backup succeeded: $BACKUP_DIR"
else
echo "Backup failed" >&2
fi
Output:
Backup succeeded: /var/backups/website-2026-08-04
Walking through what happens here: $(date +%Y-%m-%d) runs in a subshell (a forked child process) and its printed output is substituted back into the parent shell as a string, building today’s date into BACKUP_DIR. cp -r then walks the source directory tree; for each subdirectory it encounters, it calls the kernel’s mkdir at the corresponding destination path, and for each regular file, it opens the source for reading, creates a new file at the destination, and copies the data block by block until end-of-file. Once cp exits, the shell sets the special variable $? to its exit status: 0 if every file copied without error, non-zero otherwise. The script reads $? immediately, before running any other command that would overwrite it, and branches accordingly. Both path variables are quoted ("$SOURCE_DIR", "$BACKUP_DIR") so that a space or special character in either path can’t cause word-splitting into unintended extra arguments.
Common Mistakes
Mistake 1: Forgetting -r for a directory
Running cp on a directory without -r fails immediately:
cp ~/projects/website /var/backups/
This prints cp: -r not specified; omitting directory '/home/alice/projects/website' and copies nothing. cp refuses on purpose, since a directory copy can be far larger and slower than a single file copy — it will not guess that you meant to recurse. Add -r:
cp -r ~/projects/website /var/backups/
Mistake 2: Overwriting a file silently
By default cp overwrites an existing destination without asking:
cp notes.txt notes_backup.txt
If notes_backup.txt already held a different, unsaved version of your notes, that content is now gone — cp gave no warning before replacing it. Use -i to be prompted before any overwrite (good for interactive use), or -n in scripts where you want existing files left untouched with no prompt at all:
cp -i notes.txt notes_backup.txt
Mistake 3: Unquoted variables in scripts
A filename containing a space breaks an unquoted variable expansion:
cp $file $dest
If file="Meeting Notes.txt", the unquoted $file word-splits into two separate arguments, Meeting and Notes.txt, and cp either fails with “no such file” or copies the wrong thing entirely. Always quote variable expansions:
cp "$file" "$dest"
Best Practices
- Use
-iwhen copying interactively at the terminal so an accidental overwrite always asks first. - In scripts, prefer explicit
-n(never overwrite) or a deliberate-f(force) rather than relying oncp‘s silent default-overwrite behavior — make the intent visible in the command itself. - Use
-afor backups and full directory clones; it preserves permissions, ownership, timestamps, and symlinks in one flag instead of remembering several. - Always quote path and variable expansions (
"$file","$dest") to avoid word-splitting on filenames with spaces. - Add
-vwhen copying many files so you get a visible record of exactly what was copied where. - For large trees, incremental backups, or copies across a network, consider
rsyncinstead — it only transfers changed data and can resume, whereascpalways copies everything from scratch. - Remember that a nonexistent destination directory name causes
cp -rto rename the copy, not nest it — create the destination directory first withmkdir -pif you want the source folder to end up nested inside it.
Practice Exercises
- You are about to edit
/etc/nginx/nginx.conf. Before touching it, make a safety copy namednginx.conf.bakin the same directory. Check afterward withls -lthat both files exist. - You have a directory
~/projects/apithat you want backed up to/var/backups/api-backupwith permissions, ownership, and timestamps all preserved exactly. Write the single command that does this, then verify withls -lthat the timestamps match the originals. - You have three log files,
app.log,error.log, andaccess.log, in your home directory. Copy all three into an existing directory~/archive/logs/in one command, printing each filename as it is copied.
Summary
cpcreates an independent copy of a file’s data in a new inode — it does not link the two files together.- Copying a directory requires
-r(or-R);cprefuses to recurse by default. - If the destination is an existing directory, the source is copied inside it under its original name; if the destination doesn’t exist,
cpcreates it with that exact name. - By default
cppreserves permission bits but resets ownership and timestamps to the moment of copying; use-por-ato preserve more. cpoverwrites an existing destination file silently by default — use-ito be prompted, or-nto skip existing files automatically.- Always quote variable expansions holding filenames to avoid word-splitting on spaces.
-a(archive mode) is the standard flag for full, faithful backups of a directory tree.
