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