cp
Copy a file, or a directory with -r
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.
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
Symlinks, and the flag that changes what you get
cp follows a symlink by default
cp site/latest.css copy.css && stat -c '%F %s' copy.css
latest.css points at style.css. What arrives is a regular file holding the stylesheet.
Show output
regular file 33
Copy the link itself
cp -P site/latest.css copy.css && readlink copy.css
-P (--no-dereference) copies the symlink as a symlink, target text and all. It is still relative, so it now points at a style.css beside copy.css rather than the one in site/.
Show output
style.css
-a keeps links across a whole tree
cp -a site published && stat -c '%F' published/latest.css
-d is -P plus preserving hard links, and -a includes it. A cp -r without either turns every symlink in the tree into a full copy of whatever it pointed at.
Show output
symbolic link
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'