mv

Rename a file, or move it somewhere else

Updated 2026-08-28

Debian has no rename command installed by default, and mv does that job instead. Renaming and moving are one operation: mv changes an entry in a directory, and whether the old and new entries live in the same directory is not something it needs to care about.

That holds only within one filesystem. Move a file to another one and mv copies the bytes and deletes the original, so the file gets a new inode, the command takes as long as the file is big, and an interrupted move can leave a partial copy behind.

mv overwrites an existing destination without asking. Moving a directory onto a name that already exists puts it inside rather than replacing it, which is the same trap cp -r sets.

Sample files used on this page

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

site the tree the cp 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.
25 outputs, collapsed by default

Renaming and moving

There is no separate rename command on Debian. mv old new is a rename when both names are in the same directory and a move when they are not, and it is the same operation either way.

Rename a file

mv notes.txt README.txt && ls *.txt

The file is left under its new name and nowhere else. Unlike cp, nothing is left behind.

Show output
README.txt

Move a file into a directory

mv notes.txt site/backups/ && ls site/backups

A destination that is a directory means "in here, keeping the name". The directory has to exist; mv never creates one, which is what mkdir is for.

Show output
notes.txt

Move several files at once

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

With more than two arguments the last has to be a directory, as it does for cp.

Show output
index.html
style.css

Move whatever a glob matched

mv site/*.css site/backups/ && ls site/backups

The shell expands the glob and hands mv the list, so the symlink latest.css is moved alongside the file it points at. mv sees filenames and nothing else.

Show output
latest.css
style.css

Say what was renamed

mv -v notes.txt README.txt

-v (--verbose) says renamed for a move within one filesystem. Across filesystems it says copied instead, which is a useful thing to see on a move you expected to be instant.

Show output
renamed 'notes.txt' -> 'README.txt'

Rename a directory

mv site/backups site/archive && ls site

Directories need no flag. mv is renaming an entry in the parent directory, whether that entry is a file or a tree of ten thousand.

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

Put the destination first

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

-t (--target-directory) is the form to use with xargs or find -exec, where the filenames arrive at the end of the command.

Show output
index.html
style.css

Overwriting is the default here too

An existing destination is replaced without warning

mv notes.txt site/index.html && cat site/index.html

The page that was in index.html is gone and notes.txt no longer exists either, so there is nothing to put back.

Show output
Rebuild before deploying.

Ask first

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

-i (--interactive) prompts per file. The < /dev/null answers nothing, which mv reads as no; at a terminal you would type y or n.

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

Refuse silently instead

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

-n (--no-clobber) leaves both files where they are. The exit status is still zero, so a script that cares whether the move happened has to check for itself.

Show output
<!doctype html>

Keep the file being replaced

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

The old index.html becomes index.html.~1~ and the move goes ahead. numbered never reuses a backup name, where plain --backup overwrites its single ~ copy on the second run.

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

Where the directory ends up

Moving a directory onto a name that already exists puts it inside, exactly as cp -r does. This is the surprise that arrives on the second run of a command that worked the first time.

A destination that does not exist is a rename

mv site published && ls published

published is created and site is gone. One entry changed in the parent directory.

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

A destination that does exist is a move into it

mkdir published && mv site published && ls published

published/site now holds the tree. Nothing is lost, but the path everything sits at has changed, which breaks the next command in a script that assumed the first case.

Show output
site

Insist on the destination name

mkdir published && mv -T site published; ls published

-T (--no-target-directory) refuses to treat the destination as a directory to move into, so site replaces the empty published rather than landing inside it. On a destination that is a non-empty directory it fails instead.

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

mv will not move a directory into itself

mv site site/backups; ls site

Caught before anything is touched. mv compares the two resolved paths first, and the listing afterwards shows the tree intact. Not every recursive operation is this careful: rm -r on a symlink is the counter-example.

Show output
mv: cannot move 'site' to a subdirectory of itself, 'site/backups/site'
assets
backups
index.html
latest.css
style.css

Ask for a file that is not there

mv missing.txt elsewhere.txt

The message goes to standard error and the exit status is 1. Nothing partial happens, because there was nothing to move.

Show output
mv: cannot stat 'missing.txt': No such file or directory

A rename is not a copy

Within one filesystem mv changes a directory entry and touches no data at all, however large the file is. Across filesystems it copies the bytes and deletes the original, and that difference shows up in the timing, in the inode and in what an interrupted move leaves behind.

The file keeps its inode, so nothing was copied

before=$(stat -c %i notes.txt); mv notes.txt README.txt; [ "$before" = "$(stat -c %i README.txt)" ] && echo "same inode, so nothing was copied"

The inode is the file; the name is an entry pointing at it. Renaming a 10GB file inside one filesystem is as quick as renaming an empty one.

Show output
same inode, so nothing was copied

Across filesystems it really is a copy

before=$(stat -c %i notes.txt); mv notes.txt /dev/shm/notes.txt; [ "$before" = "$(stat -c %i /dev/shm/notes.txt)" ] && echo same || echo "new inode, so the bytes were copied"; rm -f /dev/shm/notes.txt

/dev/shm is a tmpfs, so it is a different filesystem from $HOME. The new file has a new inode, the move takes as long as the file is big, and an interruption partway can leave a truncated copy at the destination with the original still in place.

Show output
new inode, so the bytes were copied

Permission is on the directory, not the file

mkdir locked && chmod 500 locked && mv notes.txt locked/

locked is readable and searchable but not writable. Adding an entry to a directory is a write to that directory, so the move fails whoever happens to own notes.txt. See file permissions explained.

Show output
mv: cannot move 'notes.txt' to 'locked/notes.txt': Permission denied

Replace a file in one step

cp site/index.html tmp.html && sed -i 's/deb1/deb2/' tmp.html && mv tmp.html site/index.html && cat site/index.html

A rename within one filesystem is atomic. Nothing ever reads a half-written index.html. Editing the file in place with sed -i gives no such guarantee. Use this shape for anything another process might be reading.

Show output
<!doctype html>
<title>deb2</title>

Renaming in bulk, and only when newer

There is no glob rename, so use a loop

for f in site/assets/*.svg; do mv "$f" "${f%.svg}.min.svg"; done && ls site/assets

mv *.svg *.min.svg cannot work: the shell expands both globs before mv runs, and the second matches nothing. ${f%.svg} strips the suffix one file at a time, which is parameter expansion. Bulk rename files covers the rest, including rename from the rename package.

Show output
logo.min.svg

Move only if the source is newer

touch -d 2026-01-01 old.css && mv -uv site/style.css old.css; echo "exit $?"

-u (--update) skips a destination that is the same age or newer. old.css is dated January and style.css June, so the move goes ahead.

Show output
renamed 'site/style.css' -> 'old.css'
exit 0