cp

Copy a file, or a directory with -r

Updated 2026-08-28

cp copies a file. The last argument is the destination and everything before it is a source, so a directory at the end means "into here, keeping the name".

Two of its defaults catch people out. An existing destination is overwritten in silence, with no prompt and nothing kept, which is why -i, -n and --backup exist. And a copy is a new file, so it takes today's timestamp and your umask decides its mode unless you ask for -p.

Directories need -r. Whether that leaves you with published or published/site depends on whether published existed when you ran it, which is the one to watch in a script that might run twice.

Sample files used on this page

Every example below was run against these files. Recreate them to follow along.

site the tree the mv and rm pages use as well, including latest.css, which is a symlink to style.css

site:
total 16
drwxr-xr-x 2 user user 4096 Jun 15 10:00 assets
drwxr-xr-x 2 user user 4096 Jun 15 10:00 backups
-rw-r--r-- 1 user user   36 Jun  1 09:00 index.html
lrwxrwxrwx 1 user user    9 Jun 18 09:00 latest.css -> style.css
-rw-r--r-- 1 user user   33 Jun 20 09:00 style.css

site/assets:
total 4
-rw-r--r-- 1 user user 35 Jun 10 09:00 logo.svg

site/backups:
total 0

notes.txt one line, beside site/ rather than inside it

Rebuild before deploying.
26 outputs, collapsed by default

Copying a file

cp source destination. A destination that is a directory means the copy goes inside it under the same name. Anything else is the name the copy takes.

Copy a file to a new name

cp notes.txt notes.bak && ls notes.*

The original stays where it was and the copy holds the same contents. mv does the same job without leaving the original behind.

Show output
notes.bak
notes.txt

Copy a file into a directory

cp site/index.html site/backups/ && ls site/backups

The name is unchanged. The destination directory has to exist already, because cp creates files and never directories, so a path that is not there yet wants mkdir -p first.

Show output
index.html

Copy several files at once

cp site/index.html site/style.css site/backups/ && ls site/backups

With more than two arguments the last one has to be a directory. Every argument before it is copied into it.

Show output
index.html
style.css

Say what was copied where

cp -v site/index.html published.html

-v (--verbose) prints one line per file. Worth adding to any cp -r over a tree you have not looked inside.

Show output
'site/index.html' -> 'published.html'

Put the destination first

cp -t site/backups site/index.html site/style.css && ls site/backups

-t (--target-directory) names the destination up front, so the filenames can come from somewhere else. xargs needs this form, since it appends its arguments to the end of the command.

Show output
index.html
style.css

Overwriting is the default

cp replaces an existing destination without asking and without keeping a copy of what was there.

An existing file is replaced in silence

cp site/style.css site/index.html && cat site/index.html

No prompt, no message, no backup. index.html now holds the stylesheet. What was in it is not recoverable.

Show output
body { font-family: monospace; }

Ask before each overwrite

cp -i notes.txt site/index.html < /dev/null; echo; head -1 site/index.html

-i (--interactive) prompts, and anything other than a yes leaves the destination alone. At a terminal you would type y or n. The < /dev/null answers nothing, which cp takes as a no.

Show output
cp: overwrite 'site/index.html'?
<!doctype html>

Refuse without asking

cp -n notes.txt site/index.html && head -1 site/index.html

-n (--no-clobber) skips any destination that exists. Better than -i in a script, where there is nobody to answer the prompt.

Show output
<!doctype html>

Keep the file you are about to overwrite

cp --backup=numbered notes.txt site/index.html && ls site/

The old index.html is renamed index.html.~1~ before the copy lands. --backup on its own uses a single ~ suffix and overwrites that on the second run; numbered never reuses a name.

Show output
assets
backups
index.html
index.html.~1~
latest.css
style.css

Take a backup of a file before editing it

cp notes.txt{,.bak} && ls notes.*

notes.txt{,.bak} is brace expansion: the shell turns it into notes.txt notes.txt.bak before cp runs. Nothing about it is a cp feature. It saves typing the name twice.

Show output
notes.txt
notes.txt.bak

Copying a directory

A directory needs -r. What the copy ends up called then depends on whether the destination already exists, which is why the second run of a cp -r can do something different from the first.

cp refuses a directory without -r

cp site published

It says which argument it skipped and copies nothing. -R does the same job, and so does the long form --recursive.

Show output
cp: -r not specified; omitting directory 'site'

Copy a whole tree

cp -r site published && ls published

published did not exist beforehand. It is created as a copy of site, holding the same five entries.

Show output
assets
backups
index.html
latest.css
style.css

The same command twice puts the tree inside itself

cp -r site published && cp -r site published && ls published

By the second run published exists. site is copied into it instead, giving you published/site. A cp -r that is safe to repeat needs the destination named exactly, never a directory it might land in.

Show output
assets
backups
index.html
latest.css
site
style.css

Copy the contents rather than the directory

mkdir published && cp -r site/. published/ && ls published

site/. names the contents rather than the directory. They land directly in published, however many times you run it. A deployment script that runs more than once usually wants this form.

Show output
assets
backups
index.html
latest.css
style.css

A trailing slash on the source changes nothing

mkdir published && cp -r site/ published/ && ls published

site/ is still the directory itself. This nests, exactly as the bare name did. rsync reads that same trailing slash as "the contents of", so a habit built on one of them misfires on the other.

Show output
site

What survives the copy

A copy is a new file. It gets a new timestamp by default, and its mode comes from your current umask. Several flags ask for more than that.

A plain copy is stamped with the time you made it

cp site/index.html published.html && date -r published.html +%F

site/index.html was last written in June and the copy is dated today. date -r reads a file's modification time. It stands in for ls -l because it prints nothing but the date.

Show output

Your output will differ: the date is whatever day you run the command

2026-08-28

Keep the timestamp and the mode

cp -p site/index.html published.html && date -r published.html +%F

-p (--preserve) carries over the modification time, the mode and, where you have the privilege for it, the ownership. Without root you cannot give a file away, so the owner becomes you regardless.

Show output
2026-06-01

Preserve everything across a whole tree

cp -a site published && ls -l published/index.html

-a (--archive) is -dR --preserve=all: recursive, preserving, and leaving symlinks as symlinks. It is the flag for copying a directory you intend to keep rather than read.

Show output
-rw-r--r-- 1 user user 36 Jun  1 09:00 published/index.html

Recreate the source path under the destination

mkdir dist && cp --parents site/assets/logo.svg dist && find dist -type f

--parents copies the directories named in the source path as well as the file. The layout is repeated under dist. The destination has to be a directory that already exists.

Show output
dist/site/assets/logo.svg

Make a hard link instead of a copy

cp -l notes.txt hard.txt && stat -c '%h links' notes.txt hard.txt

-l (--link) makes a second name for the same data rather than a second copy of it. Both names report a link count of two and reach the same inode. Edit one and the other shows the change.

Show output
2 links
2 links

Make a symlink instead of a copy

cp -s notes.txt link.txt && readlink link.txt

-s (--symbolic-link) writes a symlink rather than copying anything. A relative target is only allowed when the link lands in the current directory; anywhere else cp insists on an absolute path, because the text it writes is stored verbatim.

Show output
notes.txt

Copying only what changed

Skip a destination that is already up to date

mkdir published && cp -p site/style.css published/ && cp -uv site/style.css published/; echo done

The first cp -p puts a copy in place with the same timestamp, so -u (--update) finds nothing to do and -v prints nothing. The echo is the only sign the command ran.

Show output
done

Copy it once the source is newer

mkdir published && cp -p site/style.css published/ && touch site/style.css && cp -uv site/style.css published/

With the source's timestamp moved to now by touch, -u copies. For a whole tree of files, rsync over ssh answers this question better than cp does.

Show output
'site/style.css' -> 'published/style.css'