How to Force rsync to Delete Extraneous Files on Destination with –delete Flag


1 views

Many developers encounter this exact scenario when trying to maintain perfect mirror copies between directories. Let me explain what's really happening in your example and how to properly enforce deletion.

The core issue lies in how shell expansion interacts with rsync's --delete flag. When you run:

rsync -a --delete $DIR1/* $DIR2

The shell first expands $DIR1/* to the existing files (/tmp/1/b in your case). Then rsync only sees this specific file as part of the transfer, effectively telling it "only sync file b and preserve everything else."

To make rsync properly compare entire directory structures, you need to reference the containing directory itself, not just its contents:

rsync -a --delete $DIR1/ $DIR2

Notice the crucial trailing slash on $DIR1/. This tells rsync to sync the directory contents rather than the directory as an object.

Here's your script corrected:

#!/bin/sh

set -x

DIR1=/tmp/1
DIR2=/tmp/2

rm -rf $DIR1
rm -rf $DIR2

mkdir $DIR1
mkdir $DIR2

echo "foo" > $DIR1/a
echo "bar" > $DIR1/b

# Initial sync
rsync -a $DIR1/ $DIR2

# Remove a file from source
rm -f $DIR1/a

# Mirror sync with proper deletion
rsync -a --delete $DIR1/ $DIR2

# Verify results
ls -1 $DIR2

For more complex scenarios, consider these additional rsync flags:

  • --delete-before: Delete extraneous files before transfer
  • --delete-delay: Delete during transfer
  • --delete-after: Delete after transfer (default)
  • --delete-excluded: Also delete excluded files

Always test with --dry-run first when using deletion:

rsync -a --delete --dry-run $DIR1/ $DIR2

This shows what would be deleted without actually performing any operations.


When trying to maintain an exact mirror between directories, many developers encounter unexpected behavior with rsync's --delete flag. The core issue manifests when files deleted from the source directory persist in the destination despite using what appears to be the correct syntax.

The root cause lies in how shell expansion interacts with rsync's path handling. In your example:

rsync -a --delete $DIR1/* $DIR2

This actually translates to:

rsync -a --delete /tmp/1/b /tmp/2

Notice that only the existing file 'b' gets passed to rsync. The --delete option only removes files that are missing from the source file list, not from the actual source directory.

To achieve true mirroring, you need to sync the directory itself rather than its contents:

rsync -a --delete $DIR1/ $DIR2

The critical difference is the trailing slash on the source path. This tells rsync to sync the directory contents rather than treating it as a single item.

Here's the corrected version of your test script:

#!/bin/sh

set -x

DIR1=/tmp/1
DIR2=/tmp/2

rm -rf $DIR1
rm -rf $DIR2

mkdir $DIR1
mkdir $DIR2

echo "foo" > $DIR1/a
echo "bar" > $DIR1/b

rsync -a $DIR1/ $DIR2

rm -f $DIR1/a

rsync -a --delete $DIR1/ $DIR2

ls -1 $DIR2

For more robust syncing, consider these additional flags:

rsync -a --delete --delete-excluded --delete-after $DIR1/ $DIR2

Where:

  • --delete-excluded: Also delete excluded files from destination
  • --delete-after: Delete files after transfer completes (safer)

This pattern is especially useful for:

  • Deployment scripts
  • Backup systems
  • CI/CD pipelines
  • Content distribution networks

Remember to always test with --dry-run first when working with important data:

rsync -a --delete --dry-run $DIR1/ $DIR2