# Rsync filter: copying one pattern only

I am trying to create a directory that will house all and only my PDFs compiled from LaTeX. I like keeping each project in a separate folder, all housed in a big folder called LaTeX. So I tried running:

rsync -avn *.pdf ~/LaTeX/ ~/Output/


which should find all the pdfs in ~/LaTeX/ and transfer them to the output folder. This doesn't work. It tells me it's found no matches for "*.pdf". If I leave out this filter, the command lists all the files in all the project folders under LaTeX. So it's a problem with the *.pdf filter. I tried replacing ~/ with the full path to my home directory, but that didn't have an effect.

I'm, using zsh. I tried doing the same thing in bash and even with the filter that listed every single file in every subdirectory... What's going on here?

Why isn't rsync understanding my pdf only filter?

OK. So update: No I'm trying

rsync -avn --include="*/" --include="*.pdf" LaTeX/ Output/


And this gives me the whole file list. I guess because everything matches the first pattern...

TL,DR:

rsync -am --include='*.pdf' --include='*/' --exclude='*' ~/LaTeX/ ~/Output/


Rsync copies the source(s) to the destination. If you pass *.pdf as sources, the shell expands this to the list of files with the .pdf extension in the current directory. No recursive traversal happens because you didn't pass any directory as a source.

So you need to run rsync -a ~/LaTeX/ ~/Output/, but with a filter to tell rsync to copy .pdf files only. Rsync's filter rules can seem daunting when you read the manual, but you can construct many examples with just a few simple rules.

• Inclusions and exclusions:

• Excluding files by name or by location is easy: --exclude=*~, --exclude=/some/relative/location (relative to the source argument, e.g. this excludes ~/LaTeX/some/relative/location).
• If you only want to match a few files or locations, include them, include every directory leading to them (for example with --include=*/), then exclude the rest with --exclude='*'. This is because:
• If you exclude a directory, this excludes everything below it. The excluded files won't be considered at all.
• If you include a directory, this doesn't automatically include its contents. In recent versions, --include='directory/***' will do that.
• For each file, the first matching rule applies (and anything never matched is included).
• Patterns:

• If a pattern doesn't contain a /, it applies to the file name sans directory.
• If a pattern ends with /, it applies to directories only.
• If a pattern starts with /, it applies to the whole path from the directory that was passed as an argument to rsync.
• * any substring of a single directory component (i.e. never matches /); ** matches any path substring.
• If a source argument ends with a /, its contents are copied (rsync -r a/ b creates b/foo for every a/foo). Otherwise the directory itself is copied (rsync -r a b creates b/a).

Thus here we need to include *.pdf, include directories containing them, and exclude everything else.

rsync -a --include='*.pdf' --include='*/' --exclude='*' ~/LaTeX/ ~/Output/


Note that this copies all directories, even the ones that contain no matching file or subdirectory containing one. This can be avoided with the --prune-empty-dirs option (it's not a universal solution since you then can't copy a directory even by matching it explicitly, but that's a rare requirement).

rsync -am --include='*.pdf' --include='*/' --exclude='*' ~/LaTeX/ ~/Output/

• In contrast to my solution (using zsh's ** pattern), this recreates the directory structure in the target dir. I'm not sure whether this is what the OP wants... – Marcel Stimberg Sep 29 '10 at 12:08
• I want to include only one directory and exclude rest of all directory in /etc/lsyncd/lsyncd.conf.lua file. Have an any idea? – Dhaduk Mitesh May 20 at 7:01
• @DhadukMitesh I'm not familiar with lsyncd. You should ask this as a new question. – Gilles May 20 at 7:43
rsync -av --include="*/" --include="*.pdf" --exclude="*" ~/Latex/ ~/Output/ --dry-run


The default is to include everything, so you must explicitly exclude everything after including the files you want to transfer. Remove the --dry-run to actually transfer the files.

If you start off with:

--exclude '*' --include '*.pdf'


Then the greedy matching will exclude everything right off.

If you try:

--include '*.pdf' --exclude '*'


Then only pdf files in the top level folder will be transferred. It won't follow any directories, since those are excluded by '*'.

• As of 2014-03-17 this is the best answer, as it solves the original posters question exactly. Please vote it up! If you add --prune-empty-dirs (or shortcut -m) you even spare yourself many empty directories at the destination, except of course you want them as a reminder or structural blueprint. – porg Mar 17 '14 at 23:38
• Best answer, --include="*/" is key. – Martin Konicek Aug 5 '15 at 11:46
• I want to include only one directory and exclude rest of all directory in /etc/lsyncd/lsyncd.conf.lua file. Have any idea? – Dhaduk Mitesh May 20 at 7:01

If you use a pattern like *.pdf, the shell “expands“ that pattern, i.e. it replaces the pattern with all matches in the current directory. The command you are running (in this case rsync) is unaware of the fact that you tried to use a pattern.

When you are using zsh, there is an easy solution, though: The ** pattern can be used to match folders recursively. Try this:

rsync -avn ~/LaTeX/**/*.pdf ~/Output/

• Wouldn't that copy all pdfs from somewhere within the current directory and everything from ~/LaTeX/ to ~/Output? – SamB Sep 16 '10 at 18:35
• I guess you meant rsync -avn ~/LaTeX/**/*.pdf ~/Output, but the solution with --include is more scalable anyway. – Adam Byrtek Sep 16 '10 at 18:58
• Sorry, corrected the command I mistyped in a rush... I agree that the include command (in SamB's version) is better, though it is a bit more complicated and specific to rsync while the ** might become handy in other situations as well. – Marcel Stimberg Sep 16 '10 at 19:10
• Bash 4 has adopted the same feature. Oh, and you don't need rsync here, cp will do. On some systems, if there are a lot of files, it helps to do cd ~/Latex && cp -p **/*.pdf ~/Output to avoid a “command line too long” error. – Gilles Sep 29 '10 at 17:34
• Note that rsync's patterns used in the include and exclude filters also have a ** that does the same thing. You can escape *'s from other shells by putting them in quotation marks. – Dan Pritts Feb 4 '15 at 16:28

You can use find and an intermediate list of files (files_to_copy) to solve your issue. Make sure you're in your home directory, then:

find LaTeX/ -type f -a -iname "*.pdf" > files_to_copy && rsync -avn --files-from=files_to_copy ~/ ~/Output/ && rm files_to_copy

Tested with Bash.

• I think that find is the most robust solution, but I would opt for either using finds -exec option or using xargs. Something like: find LaTeX/ -type f -iname "*.pdf" -print0 | xargs -0 -i rsync -avn {} Output/ – Steven D Sep 27 '10 at 17:09
• Yeah... I'd suggest find as well... though I imagine rsync must be able to do this. – gabe. Sep 28 '10 at 19:49
• This is a neat solution to a harder problem as well: presumably I could use this to exclude files whose document class is standalone or which don't have a .tex file with the same name, since these will be images included in some document... – Seamus Sep 29 '10 at 12:41
• rsync option --files-from accepts reading from stdin. This would work find LaTeX/ -type f -a -iname "*.pdf" | rsync -avn --files-from=- ~/ ~/Output/ – Juan Calero Sep 20 '12 at 16:15

Judging by the "INCLUDE/EXCLUDE PATTERN RULES" section of the manpage, the way to do this is

rsync -avn --include="*/" --include="*.pdf" ~/Latex/ ~/Output/


The critical difference between this and kbrd's answer is the --include="*/" flag, which tells rsync to go ahead and copy any directories it finds, whatever they are named. This is needed because rsync will not recurse into a subdirectory unless it has been instructed to copy that subdirectory.

Also, note that the quotation marks prevent the shell from trying to expand the patterns to filenames relative to the current directory, and doing one of the following:

1. Succeeding and messing up your filter (not too likely in the middle of a flag like that, though you really never know when someone will make a file named --include=foo.pdf ...)

2. Failing, and potentially producing an error instead of running the command (as you've discovered zsh does by default).

• So this will copy only the PDFs and the directory structure, while kbrd's will copy the files, but ignore the structure? – Seamus Sep 17 '10 at 9:29
• Hmm. This actually still seems to try and copy everything, I guess because that's what it does without the filter, so includeing extra stuff already in there doesn't change anything. If you see what I mean... – Seamus Sep 17 '10 at 9:33
• You need --exclude="*" after the --include="*.pdf", or this will transfer everything. – jmanning2k Sep 28 '10 at 20:25
• @jmanning2k: Ah. Good to know! – SamB Sep 29 '10 at 21:18

rsync -avn --include="*.pdf" ~/Latex/ ~/Output/

• No, man rsync puts the filter after the options and before the source/destiinations. I tried this and it didn't work – Seamus Sep 16 '10 at 16:06
• Your way finds .pdf files in the current folder, but not recursively, as I want. (the a option is for archive and among other things it makes the copying recursive. – Seamus Sep 16 '10 at 16:07
• Ooops, my bad. I updated my answer. – kbyrd Sep 16 '10 at 16:43
• +1 for being so close, and giving me a clue about how to find the relevant material in the manual page. (Hopefully I even got it right. :-) – SamB Sep 16 '10 at 19:04

Here is something that should work without using find. The difference from answers already posted is the order of the filter rules. Filter rules in an rsync command work a lot like iptable rules, the first rule that a file matches is the one that is used. From the manual page:

As the list of files/directories to transfer is built, rsync checks each name to be transferred against the list of include/exclude patterns in turn, and the first matching pattern is acted on: if it is an exclude pattern, then that file is skipped; if it is an include pattern then that filename is not skipped; if no matching pattern is found, then the filename is not skipped.

Thus, you need a command as follows:

rsync -avn --include="**.pdf" --exclude="*" ~/LaTeX/ ~/Output/


Note the "**.pdf" pattern. According to the man page:

if the pattern contains a / (not counting a trailing /) or a "**", then it is matched against the full pathname, including any leading directories. If the pattern doesn’t contain a / or a "**", then it is matched only against the final component of the filename. (Remember that the algorithm is applied recursively so "full filename" can actually be any portion of a path from the starting directory on down

In my small test, this does work recursively down the directory tree and only selects the pdfs.

• How exactly did you test? According to my understanding of the documentation and my experimental verification, your command should only copy *.pdf in the toplevel directory (but not ~/LaTeX/foo/bar.pdf). – Gilles Sep 28 '10 at 19:25
• @Gilles Crud. You are right. I swore I tested this and it worked, but I can't seem to recreate it. And now that I actually read the man page that I quoted, it makes sense that it doesn't work. Grumble. – Steven D Sep 28 '10 at 20:10
• Well, I figured out where my test was wrong. My "small test" was on a directory that has .tex and .pdf files of my own. I then created a "test" subdirectory and a test.pdf and test.tex in that subdir. However, I failed to notice that there was a test.pdf in my top level dir, likely because of some quick one of LaTeX experiment I did. – Steven D Sep 28 '10 at 20:14
• I still don't understand the **. Would be nice to have example of it. ;) – buhtz Oct 6 '17 at 9:59

This is my preferred solution:

find source_dir -iname '*.jpg' -print0 |  rsync -0 -v --files-from=- . destination_dir/


The find command is easier to understand than the include/exclude rules of rsync :-)

If you want to copy only pdf files, just change .jpg to .pdf