mv
Rename a file, or move it somewhere else
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.
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>
Symlinks and awkward names
mv moves the link, never what it points at
mv site/latest.css moved.css && readlink moved.css
style.css stays where it was. The symlink is now in your current directory, still holding the text style.css. Nothing beside it goes by that name, so it resolves to nothing.
Show output
style.css
A trailing slash on a symlink is an error
mv site/latest.css/ moved.css
The slash asserts that latest.css is a directory. mv resolves the link, finds a regular file and refuses. rm treats that same slash quite differently, at the cost of a directory.
Show output
mv: cannot stat 'site/latest.css/': Not a directory
Rename a file whose name begins with a dash
printf x > ./-output.html && mv -- -output.html saved.html && ls saved.html
-- ends the options, so the name after it is read as a filename however it starts. ./-output.html does the same job without needing the flag.
Show output
saved.html
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