Overview

Rsync’s --include and --exclude options let you selectively transfer files based on pattern matching. Rules are evaluated in order from first to last, and the first matching rule wins. This makes rule ordering critical.

Basic Syntax

rsync -avP \
  --include='PATTERN' \
  --exclude='PATTERN' \
  /source/directory/ /destination/directory/
  • --include='PATTERN' — transfer files matching this pattern
  • --exclude='PATTERN' — skip files matching this pattern
  • Rules are evaluated top to bottom; first match wins

Using ** for Recursive Matching

The ** wildcard matches any path component, including subdirectories. This is especially useful when your files are nested at unknown depths.

Pattern Matches
* Any filename in the current directory
** Any file at any depth
*.m2ts All .m2ts files in the current directory
**/*.m2ts All .m2ts files at any depth
**/reports-monday** Any file containing reports-monday at any depth

Selective Transfer with Include/Exclude

The most common pattern is to include specific files and exclude everything else:

rsync -avP \
  --include='**/*-monday*' \
  --include='**/*-special-edition*' \
  --include='**/*-weekly-summary*' \
  --include='**/*-highlights*' \
  --exclude='*' \
  /source/directory/ /destination/directory/

This transfers only files matching the four include patterns and ignores everything else.

How Rule Order Works

rsync -avP \
  --include='**/*-monday*'         \  # 1. Include monday files
  --include='**/*-weekly-summary*' \  # 2. Include weekly summaries
  --exclude='*'                    \  # 3. Exclude everything else
  /source/directory/ /destination/directory/

A file is checked against rule 1 first. If it matches, it is included. If not, it moves to rule 2, and so on. The final --exclude='*' acts as a catch-all to reject anything not previously matched.

Practical Example: One File Per Category Per Week

Suppose you have a directory of daily recordings and you only want to transfer one episode per show per week, plus all special editions:

/recordings/
├── 2025-02-17-show-a-monday.m2ts
├── 2025-02-18-show-a-tuesday.m2ts
├── 2025-02-19-show-a-wednesday.m2ts
├── 2025-02-17-show-b-episode-12.m2ts
├── 2025-02-18-show-c-episode-45.m2ts
├── 2025-02-17-show-a-special-edition.m2ts
└── 2025-02-20-unrelated-program.m2ts

To transfer only Monday episodes of Show A, all of Show B, all of Show C, and all special editions:

rsync -avP \
  --include='**/*show-a-monday*' \
  --include='**/*show-a-special-edition*' \
  --include='**/*show-b*' \
  --include='**/*show-c*' \
  --exclude='*' \
  /recordings/ /backup/recordings/

This results in transferring:

  • 2025-02-17-show-a-monday.m2ts (matched by rule 1)
  • 2025-02-17-show-a-special-edition.m2ts (matched by rule 2)
  • 2025-02-17-show-b-episode-12.m2ts (matched by rule 3)
  • 2025-02-18-show-c-episode-45.m2ts (matched by rule 4)

While skipping:

  • 2025-02-18-show-a-tuesday.m2ts (excluded by catch-all)
  • 2025-02-19-show-a-wednesday.m2ts (excluded by catch-all)
  • 2025-02-20-unrelated-program.m2ts (excluded by catch-all)

Using Filter Files

For complex rule sets, store filters in a file:

# filters.txt
+ **/*show-a-monday*
+ **/*show-a-special-edition*
+ **/*show-b*
+ **/*show-c*
- *

Then reference it with --filter:

rsync -avP --filter='merge filters.txt' /recordings/ /backup/recordings/

In filter files, + means include and - means exclude.

Dry Run

Always preview what will be transferred before committing:

rsync -avP --dry-run \
  --include='**/*show-a-monday*' \
  --exclude='*' \
  /recordings/ /backup/recordings/

The --dry-run flag shows what rsync would do without actually copying any files.

Common Pitfalls

  • Rule order matters — an early --exclude='*' will block all subsequent includes
  • Trailing slash on source/source/ copies contents; /source copies the directory itself
  • Single * vs ** — use ** when files may be nested in subdirectories
  • Quoting patterns — always single-quote patterns to prevent shell glob expansion

Updated: