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 dest does not exist, cp creates a new file with that exact name and copies the source’s contents into it.
  • If dest exists and is a regular file, cp overwrites its contents — silently, by default, with no warning.
  • If dest exists and is a directory, cp copies 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 -i when 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 on cp‘s silent default-overwrite behavior — make the intent visible in the command itself.
  • Use -a for 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 -v when 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 rsync instead — it only transfers changed data and can resume, whereas cp always copies everything from scratch.
  • Remember that a nonexistent destination directory name causes cp -r to rename the copy, not nest it — create the destination directory first with mkdir -p if you want the source folder to end up nested inside it.

Practice Exercises

  1. You are about to edit /etc/nginx/nginx.conf. Before touching it, make a safety copy named nginx.conf.bak in the same directory. Check afterward with ls -l that both files exist.
  2. You have a directory ~/projects/api that you want backed up to /var/backups/api-backup with permissions, ownership, and timestamps all preserved exactly. Write the single command that does this, then verify with ls -l that the timestamps match the originals.
  3. You have three log files, app.log, error.log, and access.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

  • cp creates 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); cp refuses 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, cp creates it with that exact name.
  • By default cp preserves permission bits but resets ownership and timestamps to the moment of copying; use -p or -a to preserve more.
  • cp overwrites an existing destination file silently by default — use -i to be prompted, or -n to 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.