When working with rsync, a common but tricky scenario is needing to exclude all directories except a few specific ones you want to sync. The directory structure might look like:
source_dir/
some_dir/
another_dir/
some_file.php
some_other_file.txt
include_this_dir/
include_that_dir/
yet_another_dir/
Most solutions you'll find online suggest using multiple --exclude
parameters or complex pattern matching, but these approaches have limitations:
- They require explicitly listing every directory to exclude
- They don't handle new directories that might appear later
- They often have unexpected behavior with file exclusion
Here's the correct way to include only specific directories while excluding everything else:
rsync -av --dry-run --delete \
--include='include_this_dir/' \
--include='include_that_dir/' \
--include='*/' \
--exclude='*' \
source_dir/ dest_dir
The magic happens through careful ordering of include/exclude filters:
--include='include_this_dir/'
- Explicitly includes our target directories--include='include_that_dir/'
- Another explicit include--include='*/'
- Allows rsync to scan all directories (but not their contents yet)--exclude='*'
- Excludes everything else
When implementing this solution, keep in mind:
# This will NOT work (order matters!):
rsync -av --exclude='*' --include='special_dir/' source dest
# This WILL work:
rsync -av --include='special_dir/' --exclude='*' source dest
Also note that trailing slashes matter in your patterns. special_dir
is different from special_dir/
.
For complex scenarios, you might prefer using a filter file:
# rsync_filter.txt
+ include_this_dir/
+ include_that_dir/
+ */
- *
Then run:
rsync -av --filter='. rsync_filter.txt' source_dir/ dest_dir
Always test with --dry-run
first to verify your patterns:
rsync -avn --delete \
--include='include_this_dir/' \
--include='include_that_dir/' \
--include='*/' \
--exclude='*' \
source_dir/ dest_dir
Suppose you need to sync only node_modules
and src
from a project directory:
rsync -av --delete \
--include='node_modules/' \
--include='src/' \
--include='*/' \
--exclude='*' \
project/ backup_server:/backups/project/
- If nothing is being included, check your filter order
- Use
-vv
for verbose output to see filtering decisions - Remember rsync processes filters in order until first match
- Trailing slashes matter - they distinguish files from directories
When working with rsync, one common yet tricky scenario is syncing only specific directories while excluding all others by default. This pattern becomes crucial when:
- You want future-proof exclusions (newly added directories should be automatically excluded)
- The source contains numerous directories where manual exclusion would be tedious
- You need precise control over directory-level synchronization
Many developers attempt solutions like:
rsync -av --exclude="*" --include="include_this_dir/" --include="include_that_dir/" source/ dest/
But encounter these issues:
- Excluded parent directories prevent child directory matching
- File-level patterns don't properly handle directory structures
- Order of include/exclude rules affects the outcome
After extensive testing across rsync versions 3.x, here's the reliable approach:
rsync -av \
--include="/include_this_dir/" \
--include="/include_that_dir/" \
--include="/include_this_dir/**" \
--include="/include_that_dir/**" \
--exclude="*" \
--exclude="*/" \
source_dir/ dest_dir/
The magic happens through these carefully ordered rules:
--include="/dir_name/"
explicitly includes the directory itself--include="/dir_name/**"
includes all contents recursively--exclude="*"
handles all files--exclude="*/"
catches all other directories
Case 1: Including multiple specific directories
rsync -av \
--include="/dir1/" --include="/dir2/" --include="/special_assets/" \
--include="/dir1/**" --include="/dir2/**" --include="/special_assets/**" \
--exclude="*" \
--exclude="*/" \
project/ backup/
Case 2: With delete option to maintain exact mirror
rsync -av --delete \
--include="/config/" --include="/config/**" \
--include="/templates/" --include="/templates/**" \
--exclude="*" \
--exclude="*/" \
source_server:/path/ /local_mirror/
Always test with --dry-run
first. For verbose output:
rsync -av --dry-run --verbose \
--include="/important/" --include="/important/**" \
--exclude="*" \
--exclude="*/" \
source/ destination/ | grep -E "^(>|c)"
This filters output to show only actual transfer decisions.