How to Move Files and Directories in Linux with mv

How to Move Files and Directories in Linux with mv

Reading time1 min
#linux#mv#command-line#file-management#bash

How to Move Files and Directories in Linux with mv

mv moves and renames files and directories. There is no separate rename command for a single file: renaming is just a move within the same directory.

Basic syntax

mv [options] SOURCE DEST
mv [options] SOURCE... DIRECTORY
mv report.txt ~/Documents/              # move into a directory
mv report.txt ~/Documents/q3.txt        # move and rename
mv draft.md final.md                    # rename in place
mv /tmp/build.log /var/log/my-app/      # absolute paths

A trailing slash on the destination is a good habit. mv report.txt archive/ fails with an error if archive does not exist. Without the slash, mv report.txt archive would silently rename the file to archive.

Move files to another directory

With more than two arguments, the last one must be an existing directory:

mv a.txt b.txt c.txt backup/
mv *.log logs/
mv ~/Downloads/*.pdf ~/Documents/invoices/

The * glob does not match hidden files (names starting with a dot). To include them in bash:

shopt -s dotglob
mv src/* dest/
shopt -u dotglob

To move files selected by find, use -t, which puts the target directory first. That is the form -exec ... {} + and xargs need:

find . -maxdepth 1 -name '*.csv' -mtime +30 -exec mv -t archive/ {} +

Move or rename a directory

Directories move the same way. Unlike cp, mv needs no -r.

mv my-app /opt/
mv old-name new-name

The result depends on whether the destination already exists:

  • mv project backup when backup does not exist renames project to backup.
  • When backup is an existing directory, project moves inside it and becomes backup/project.
  • If backup/project already exists and is not empty, mv fails with Directory not empty. It never merges two directories. Use rsync for that (see below).

-T treats the destination as a plain name, so mv -T project backup either renames or fails, and never nests one directory inside the other.

Avoid overwriting files

By default mv silently replaces an existing file at the destination. These options make it safer:

mv -i report.txt archive/                  # ask before overwriting
mv -n report.txt archive/                  # never overwrite
mv -b report.txt archive/                  # keep the old file as report.txt~
mv --backup=numbered report.txt archive/   # report.txt.~1~, report.txt.~2~, ...
mv -u *.txt archive/                       # only when the source is newer
mv -v *.txt archive/                       # print every move

If you combine -i, -n and -f, the last one wins. RHEL-family systems alias mv to mv -i for root, which is why it prompts there and not on Ubuntu. command mv bypasses the alias.

File names that start with a dash look like options. Put -- before them or prefix the path:

mv -- -old-name.txt new-name.txt
mv ./-old-name.txt new-name.txt

Rename many files at once

mv renames one file per call, so use a loop:

for f in *.txt; do mv -- "$f" "${f%.txt}.md"; done

${f%.txt} strips the .txt suffix from the name. The rename tool does the same in one command, but there are two different versions:

# Debian, Ubuntu: Perl rename (sudo apt install rename), -n is a dry run
rename -n 's/\.txt$/.md/' *.txt

# RHEL family: util-linux rename, takes "from to files"
rename .txt .md *.txt

Moving across filesystems

Within one filesystem, mv is a rename. It finishes instantly whatever the size, because no data moves, and ownership and timestamps stay as they were.

When source and destination are on different filesystems (another disk, partition, NFS mount, or /tmp on tmpfs), mv copies the data and then deletes the source. That takes time and needs free space at the destination. mv removes each source only after it has been copied, so an interrupted move leaves the original in place and possibly a partial copy at the destination. As a regular user, it also cannot keep the ownership of files that belong to other users.

For large trees, rsync is the better tool. It shows progress, can resume, and merges into an existing directory:

rsync -aHAX --info=progress2 /data/old/ /mnt/new/
rsync -aHAX --dry-run --itemize-changes /data/old/ /mnt/new/   # verify: should list nothing
rm -rf /data/old

-a keeps permissions, times and symlinks, -H keeps hard links, -A and -X keep ACLs and extended attributes. The trailing slash on the source matters: /data/old/ copies the contents, /data/old creates old inside the destination.

To move and delete in one pass:

rsync -a --remove-source-files /data/old/ /mnt/new/
find /data/old -type d -empty -delete

--remove-source-files deletes files but leaves the empty directory tree behind, which the find line cleans up.

To move a file to another machine, copy it over SSH and remove the local copy once the transfer succeeded:

rsync -a --remove-source-files backup.tar.gz user@203.0.113.10:/srv/backups/

Permissions and SELinux

Moving a file needs write and execute permission on both the source and the destination directory, not on the file itself. In directories with the sticky bit, like /tmp, you can only move out files you own. For system paths use sudo:

sudo mv my-app.service /etc/systemd/system/

On RHEL, Fedora and other SELinux systems, mv keeps the file's original security context. A unit file or web content moved from your home directory keeps a home-directory label, and the service then gets "Permission denied" even though the mode looks right. Reset the label after the move, or copy instead of moving:

sudo restorecon -v /etc/systemd/system/my-app.service

Quick reference

mv file.txt dir/                         # move into a directory
mv old.txt new.txt                       # rename
mv a b c dir/                            # several files
mv -t dir/ file1 file2                   # target first (for find/xargs)
mv -i file.txt dir/                      # ask before overwriting
mv -n file.txt dir/                      # never overwrite
mv --backup=numbered file.txt dir/       # keep old versions
mv -v src/* dest/                        # verbose
mv -T src-dir dest-dir                   # rename, never nest
for f in *.txt; do mv -- "$f" "${f%.txt}.md"; done
rsync -aHAX --info=progress2 src/ dest/  # big or cross-disk moves