rsync using regex to include only some files

rsync doesn't speak regex. You can enlist find and grep, though it gets a little arcane. To find the target files:

find a/ |
grep -i 'name'

But they're all prefixed with "a/" - which makes sense, but what we want to end up with is a list of include patterns acceptable to rsync, and as the "a/" prefix doesn't work for rsync I'll remove it with cut:

find . |
grep -i 'name' |
cut -d / -f 2-

There's still a problem - we'll still miss files in subdirectories, because rsync doesn't search directories in the exclude list. I'm going to use awk to add the subdirectories of any matching files to the list of include patterns:

find a/ |
grep -i 'name' |
cut -d / -f 2- |
awk -F/ '{print; while(/\//) {sub("/[^/]*$", ""); print}}'

All that's left is to send the list to rsync - we can use the argument --include-from=- to provide a list of patterns to rsync on standard input. So, altogether:

find a/ |
grep -i 'name' |
cut -d / -f 2- |
awk -F/ '{print; while(/\//) {sub("/[^/]*$", ""); print}}' |
rsync -avvz --include-from=- --exclude='*' ./a/ ./b/

Note that the source directory 'a' is referred to via two different paths - "a/" and "./a/". This is subtle but important. To make things more consistent I'm going to make one final change, and always refer to the source directory as "./a/". However, this means the cut command has to change as there will be an extra "./" on the front of the results from find:

find ./a/ |
grep -i 'name' |
cut -d / -f 3- |
awk -F/ '{print; while(/\//) {sub("/[^/]*$", ""); print}}' |
rsync -avvz --include-from=- --exclude='*' ./a/ ./b/

I would suggest to use the filter option of rsync. For your example just type:

rsync -vam -f'+ *[Nn][Aa][Mm][E]*' -f'+ */' -f'- *' a b

the first filter rule tells rsync what patterns to include. The second rule is needed to tell rsync to inspect all directories on its traversal. To prevent empty dirs from inclusion they are excluded explicitly by -m option. The last filter rule tells rsync to dispose all remaining patterns that still didn't match so far.


If you use ZSH then you can use the (#i) flag to turn off case sensitivity. Example:

$ touch NAME
$ ls (#i)*name*
NAME

ZSH also supports exclusions, which are specified just like the regular path but they have an initial ~

$ touch aa ab ac
$ ls *~*c
aa ab

You can chain exclusions:

$ ls *~*c~*b
aa

Finally you can specify what kind of file you want returned (directory, file, etc). This is done with (/) for directory and (.) for file.

$ touch file
$ mkdir dir
$ ls *(.)
file

Based on all this, I would do that command as:

rsync -avvz *(/) (#i)*name* ./a/ ./b/

(I don't see a need for an exclusion with these selectors)