Practical Linux, Windows Server and cloud guides for IT pros.

How to Safely Rename, Copy, Move, Sync and Delete Files in Linux

Tested Linux examples for renaming, moving, copying, syncing and deleting files safely, including overwrite checks and rsync dry runs.

Filed under

Published

Written by

Last updated

Linux file management guide for mv, cp, rsync and rm commands

Linux file commands are easy to memorize and just as easy to misuse. Most accidents do not happen because someone forgets that mv moves a file or that rm deletes one. They happen because the destination already exists, a variable is empty, a path contains spaces, or an rsync --delete command points at the wrong directory.

The safest routine is straightforward:

  1. Inspect the source and destination.
  2. Preview the operation where possible.
  3. Make the change.
  4. Verify the result.
  5. Delete the old data only after verification.

That process takes a few extra seconds and is considerably faster than restoring files from backup.

This guide was updated for Ubuntu 26.04 LTS and rsync 3.4.1. Ubuntu 26.04 uses Rust-based uutils for many core commands, but cp, mv and rm continue using GNU Coreutils due to unresolved compatibility and security issues. The Ubuntu 26.04 repositories provide rsync 3.4.1.

Several commands below use GNU or non-POSIX options. These are identified where relevant.

Linux File Command Quick Reference

JobSafer commandImportant detail
Rename a directorymv -iT -- old-name new-name-T prevents the destination from being treated as a directory to move into
Move a filemv -iv -- report.txt /srv/reports/-i asks before overwriting an existing file
Archive-copy a directorycp -aivT -- app app.backup-a preserves supported metadata; -T prevents accidental nesting
Preview an rsync copyrsync -a --dry-run --itemize-changes -- source/ destination/The source slash means “copy the contents”
Verify an rsync copyrsync -a --dry-run --checksum --itemize-changes -- source/ destination/Reads both copies and compares file contents
Preview a mirrorrsync -a --dry-run --itemize-changes --delete -- source/ mirror/Destination-only files will be removed during the real run
Remove a directory treerm -Ir --one-file-system -- old-releaseGNU options; review the prompt carefully

The standalone -- argument ends option processing. It prevents a filename such as -draft from being interpreted as a command-line option.

Quoting every variable and pathname is equally important:

mv -- "$source" "$destination"

The quotes preserve spaces and shell metacharacters in the path. The -- protects names beginning with a hyphen.

Check Which Command Implementation You Are Using

Before relying on implementation-specific options, check the installed versions:

cp --version | head -n 1
mv --version | head -n 1
rm --version | head -n 1
rsync --version | head -n 1

On a standard Ubuntu 26.04 installation, cp, mv and rm should report GNU Coreutils. Other commands may report uutils coreutils.

You can also inspect the executable selected by your shell:

command -v cp
command -v mv
command -v rm
command -v rsync

Options such as cp -T, mv -T, rm --one-file-system and GNU find -printf should not be assumed to exist on every Unix-like system. Check the local manual before using them in portable scripts.

Before Changing Anything

Confirm your current location and inspect both sides of the proposed operation:

pwd
ls -lah
ls -ld -- source destination
find source -maxdepth 2 -printf '%y %m %u:%g %s %p\n' | sort

These commands answer different questions:

  • pwd confirms your current directory.
  • ls -lah shows the surrounding files, including hidden entries.
  • ls -ld inspects the named directory entries rather than listing their contents.
  • find produces a compact view of the source tree.

The find -printf format above displays:

  • File type
  • Permission mode
  • Owner and group
  • File size
  • Full pathname

find -printf is a GNU Findutils extension and is not portable across all Unix implementations.

For important data, also check the source and destination filesystems:

df -hT -- source destination

A cross-filesystem mv behaves differently from a rename on the same filesystem. Instead of changing a directory entry, it must copy the data and then remove the source.

Rename a Directory Without Moving It Inside Another Directory

A normal two-argument mv command has an important ambiguity:

mv project-old project-archive

When project-archive already exists as a directory, mv may place project-old inside it:

project-archive/project-old/

On GNU systems, use -T or --no-target-directory when the destination must be treated as the exact destination entry:

mv -iT -- project-old project-archive

The options mean:

  • -i prompts before replacing an existing destination.
  • -T treats the final operand as the destination itself.
  • -- ends option parsing.

GNU documents -T as the option that prevents the last operand from being treated specially when it names a directory or a symbolic link to a directory.

Be aware that -T prevents accidental nesting; it does not guarantee that the destination is absent. For example, mv may replace an existing empty destination directory after confirmation.

Rename a Directory in a Script

Interactive prompts are unsuitable for unattended scripts. Validate the source and destination explicitly:

#!/bin/sh
set -eu

source='project-old'
destination='project-archive'

if [ ! -e "$source" ] && [ ! -L "$source" ]; then
    printf 'Source does not exist: %s\n' "$source" >&2
    exit 1
fi

if [ -e "$destination" ] || [ -L "$destination" ]; then
    printf 'Refusing to replace existing destination: %s\n' \
        "$destination" >&2
    exit 1
fi

mv -T -- "$source" "$destination"

Testing both -e and -L matters because -e returns false for a dangling symbolic link. Without the -L check, a broken destination symlink could go unnoticed.

This check-and-move pattern is appropriate for normal administrative scripts, but it is not safe against every concurrency race. Another process could create the destination between the test and the mv command. Workflows with concurrent writers require locking or a deployment mechanism designed for atomic switching.

-T is not a POSIX option. Test scripts against every operating system they must support.

Move a File Into a Known Directory

To move a file into an existing directory:

mv -iv -- report.txt /srv/reports/

Before running it, confirm that the destination exists and is the expected directory:

ls -ld -- /srv/reports/

The trailing slash communicates that /srv/reports/ is intended to be a directory. If it does not exist, mv should fail rather than silently creating a file called reports.

For multiple source files on GNU systems, -t makes the target directory explicit:

mv -iv -t /srv/reports/ -- report-1.txt report-2.txt

The -t form is useful in scripts because the destination is supplied as an option rather than inferred from the final operand.

Copy a Directory While Preserving Metadata

For an archive-style copy on GNU/Linux, use cp -a rather than a basic recursive copy:

cp -aivT -- app app.backup

The options perform the following work:

  • -a enables archive mode.
  • -i asks before overwriting existing entries.
  • -v displays copied paths.
  • -T treats app.backup as the exact destination.

GNU describes cp -a as equivalent to recursive copying with link and metadata preservation enabled. It attempts to preserve permissions, ownership, timestamps, links, ACL-related mode information, extended attributes and security context where supported.

Why -T Matters for cp

Without -T, this command:

cp -aiv -- app app.backup

can create the following layout when app.backup already exists:

app.backup/app/

Adding -T prevents that extra directory level:

cp -aivT -- app app.backup

However, -T does not make the destination exclusive. If app.backup already exists as a directory, cp can merge the source tree into it. The -i option prompts only when an existing entry would be overwritten.

For a new standalone backup, use a unique destination:

backup="app.backup.$(date +%Y%m%d-%H%M%S)"
cp -aT -- app "$backup"

Then inspect the result:

ls -ld -- "$backup"
find "$backup" -maxdepth 2 -printf '%y %m %u:%g %s %p\n' | sort

Metadata Preservation Has Limits

Archive mode cannot guarantee that every attribute will survive every copy.

Results depend on:

  • The source and destination filesystems
  • User privileges
  • ACL support
  • Extended-attribute support
  • SELinux or other security labeling
  • User and group availability
  • Filesystem-specific flags
  • Whether the destination supports hard links, sparse files and special files

GNU also notes that cp -a can ignore some failures to preserve SELinux context or extended attributes without changing the command’s exit status. When metadata is operationally important, inspect it separately rather than relying only on a successful exit code.

Useful inspection commands include:

stat -- app app.backup
getfacl -R -- app
getfacl -R -- app.backup
getfattr -R -d -m- -- app
getfattr -R -d -m- -- app.backup

The acl and attr packages may be required for getfacl and getfattr.

Use rsync for Repeatable Copies

rsync is usually a better choice when you expect to run the same copy more than once. Ubuntu 26.04 provides rsync 3.4.1.

Install it when necessary:

sudo apt update
sudo apt install rsync

Always preview a meaningful rsync command before running it:

rsync -a --dry-run --itemize-changes -- source/ destination/

The options mean:

  • -a enables rsync archive mode.
  • --dry-run calculates the changes without applying them.
  • --itemize-changes explains what would change.
  • -- ends option parsing.

The rsync manual recommends combining --dry-run with verbose or itemised output so that the proposed operation can be reviewed before it runs.

When the preview is correct, remove --dry-run:

rsync -a --itemize-changes -- source/ destination/

This copies new and changed source files. It does not remove destination-only files, so it is an update operation rather than a true mirror.

Understand the rsync Source Trailing Slash

The trailing slash on the source changes the resulting directory layout.

Copy the Contents of the Source Directory

rsync -a -- source/ destination/

This produces:

destination/file.txt

Copy the Source Directory by Name

rsync -a -- source destination/

This normally produces:

destination/source/file.txt

The rsync documentation describes a trailing source slash as copying the directory’s contents instead of creating an additional directory level at the destination.

Practise this behaviour in a disposable directory before using rsync against production data. A misplaced slash is one of the most common causes of incorrect directory layouts.

Also ensure the destination directory exists. With a single matching source item, an absent destination path can sometimes be interpreted as a new filename rather than a directory name.

Know What rsync -a Does Not Preserve

Rsync archive mode is equivalent to:

-rlptgoD

Despite its name, -a does not preserve every available attribute. It does not automatically include:

  • ACLs
  • Extended attributes
  • Hard-link relationships
  • Access times
  • Creation times on systems that support them

The rsync manual explicitly excludes -A, -X, -H, -U and -N from archive mode.

When ACLs, extended attributes, and hard-link relationships matter, preview the following form:

rsync -aHAX --dry-run --itemize-changes -- source/ destination/

Then run it without --dry-run:

rsync -aHAX --itemize-changes -- source/ destination/

The additional options are:

  • -H: preserve hard-link relationships.
  • -A: preserve ACLs.
  • -X: preserve extended attributes.

Preserving ownership may require root privileges on the receiving side. Hard-link detection can also increase memory use on very large directory trees.

For migrations between systems where numeric ownership must remain unchanged, consider:

rsync -aHAX --numeric-ids --dry-run --itemize-changes -- \
    source/ destination/

Only use --numeric-ids when identical numeric user and group IDs have the intended meaning on both systems.

Verify the rsync Result

A second normal dry run checks whether rsync still detects changes:

rsync -a --dry-run --itemize-changes -- source/ destination/

No output usually means rsync considers the source and destination current according to its normal quick check, which compares file size and modification time.

For a stronger one-off content check, add --checksum:

rsync -a --dry-run --checksum --itemize-changes -- \
    source/ destination/

The --checksum option reads corresponding files and uses content checksums to decide whether they differ. This is more expensive because every candidate file must be read on both sides. Rsync already verifies transferred files as part of the transfer protocol; --checksum changes the pre-transfer comparison used to decide whether a file needs updating.

For smaller directory trees, diff is another useful content check:

diff -qr -- source destination

diff -qr compares file contents but does not verify every metadata attribute.

Mirror a Directory With --delete

The --delete option removes destination entries that do not exist in the source:

rsync -a --delete -- source/ mirror/

That is appropriate for a mirror. It is dangerous for an ordinary backup directory that contains historical or destination-only files.

Always preview deletions:

rsync -a --dry-run --itemize-changes --delete -- \
    source/ mirror/

Only run the real operation after reviewing every proposed deletion:

rsync -a --itemize-changes --delete -- \
    source/ mirror/

The rsync manual describes --delete as dangerous when used incorrectly and recommends a dry run before allowing deletions.

Confirm That the Source Is Mounted and Populated

An empty or incorrectly mounted source combined with --delete can create a very accurate empty mirror.

Inspect both paths first:

ls -ld -- source mirror
find source -mindepth 1 -maxdepth 1 -print
df -hT -- source mirror

For scheduled jobs, validate an expected marker file:

source='/mnt/data-source'
mirror='/srv/data-mirror'

if [ ! -d "$source" ]; then
    printf 'Source directory is unavailable: %s\n' "$source" >&2
    exit 1
fi

if [ ! -f "$source/.rsync-source-ok" ]; then
    printf 'Source marker is missing: %s\n' "$source" >&2
    exit 1
fi

if [ ! -d "$mirror" ]; then
    printf 'Mirror directory is unavailable: %s\n' "$mirror" >&2
    exit 1
fi

rsync -a --dry-run --itemize-changes --delete -- \
    "$source/" "$mirror/"

The marker should be created deliberately on the real source filesystem:

touch /mnt/data-source/.rsync-source-ok

If the mount fails and exposes an empty underlying directory, the marker will be absent and the job will stop.

Limit the Number of Deletions

For automated mirrors, --max-delete limits the deletion blast radius:

rsync -a --dry-run --itemize-changes \
    --delete \
    --max-delete=100 \
    -- source/ mirror/

During the real run, rsync stops deleting after the configured limit is reached, reports the skipped deletions and exits with status 25. Transfers and updates that occurred before the limit was reached may still have been applied, so your monitoring must treat the non-zero exit as a failure.

Choose a limit that is low enough to catch unexpected source failures but high enough for legitimate cleanup.

Delete Last, Not First

Before removing a directory tree, inspect it one final time:

ls -ld -- old-release
find old-release -maxdepth 2 -printf '%y %m %u:%g %s %p\n' | sort
du -sh -- old-release

For an interactive GNU/Linux session, the following command is safer than immediately using rm -rf:

rm -Ir --one-file-system -- old-release

The options mean:

  • -I prompts once before a recursive removal or when more than three files are named.
  • -r removes the directory tree recursively.
  • --one-file-system avoids descending into directories on a different filesystem.
  • -- ends option parsing.

GNU documents -I as a single confirmation before a recursive operation. It also describes --one-file-system as protection against recursively entering a separately mounted filesystem beneath the target.

These controls reduce risk, but they do not make deletion reversible.

--one-file-system will not protect:

  • Other valuable directories on the same filesystem
  • An incorrectly selected parent directory
  • Data referenced through every possible mount arrangement
  • Files already removed before an error occurs

When the data cannot be recreated, make and test a backup before deleting it.

Guard Against Empty Variables in Removal Scripts

Never place an unchecked variable directly into a recursive removal command:

rm -rf -- "$target"

Validate it first:

#!/bin/sh
set -eu

target='/srv/releases/old-release'

case "$target" in
    '' | '/' | '.' | '..')
        printf 'Refusing unsafe removal target: %s\n' "$target" >&2
        exit 1
        ;;
esac

if [ ! -d "$target" ]; then
    printf 'Removal target is not a directory: %s\n' "$target" >&2
    exit 1
fi

printf 'Removing directory tree: %s\n' "$target"
rm -Ir --one-file-system -- "$target"

For unattended automation, an interactive prompt is not sufficient. Use an explicit allowlist, expected path prefix, marker file, locking, and a tested backup or rollback process.

Complete Disposable Test

The following sequence creates a temporary workspace, copies a directory, renames the copy, previews an rsync operation, performs it, and verifies the result:

workdir=$(mktemp -d)

mkdir -p \
    "$workdir/source" \
    "$workdir/destination"

printf 'alpha\n' > "$workdir/source/alpha.txt"
printf 'beta\n' > "$workdir/source/file with spaces.txt"

# Create an archive-style copy at an exact destination path
cp -aivT -- \
    "$workdir/source" \
    "$workdir/source-copy"

# Rename the copied directory
mv -iT -- \
    "$workdir/source-copy" \
    "$workdir/renamed-copy"

# Preview the repeatable copy
rsync -a \
    --dry-run \
    --itemize-changes \
    -- "$workdir/source/" "$workdir/destination/"

# Apply the copy
rsync -a \
    --itemize-changes \
    -- "$workdir/source/" "$workdir/destination/"

# Verify contents using checksums
rsync -a \
    --dry-run \
    --checksum \
    --itemize-changes \
    -- "$workdir/source/" "$workdir/destination/"

# Inspect the final tree
find "$workdir" \
    -maxdepth 3 \
    -printf '%y %m %u:%g %s %p\n' |
    sort

The first rsync dry run should list both newly created files with output similar to:

>f+++++++++ alpha.txt
>f+++++++++ file with spaces.txt

The checksum verification should produce no output after a successful copy.

Inspect the workspace before removing it:

ls -ld -- "$workdir"
find "$workdir" -maxdepth 3 -printf '%y %p\n' | sort

Then remove the temporary directory:

rm -Ir --one-file-system -- "$workdir"

Final Safety Checklist

Before changing important files, confirm the following:

  • You are in the expected directory.
  • The source exists and contains the expected data.
  • The destination path is correct.
  • Variables are quoted and non-empty.
  • -- separates options from pathnames.
  • -T is used when the destination must be an exact entry.
  • The trailing slash on the rsync source matches the intended layout.
  • Every rsync --delete operation has been previewed.
  • Automated mirrors validate their source mount or marker file.
  • Important copies have been verified.
  • A usable backup exists before permanent deletion.

Primary Documentation

  • Ubuntu 26.04 LTS release notes, including the rust-coreutils transition and continued use of GNU cp, mv and rm.
  • GNU Coreutils manual covering cp, mv, rm, target-directory handling and metadata preservation.
  • Official rsync manual covering archive mode, dry runs, trailing slashes, checksums and deletion controls.
  • Ubuntu 26.04 rsync package information.

Related TurboGeek Guides

Leave a Reply

Your email address will not be published. Required fields are marked *

Find more on the site

Keep reading by topic.

If this post was useful, the fastest way to keep going is to pick the topic you work in most often.

Want another useful post?

Browse the latest posts, or support TurboGeek if the site saves you time regularly.