Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

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...

share|improve this question
    
uh, you seem to be right... I think my answer (using zsh's ** pattern) should work, though. –  Marcel Stimberg Sep 29 '10 at 9:22
add comment

7 Answers 7

up vote 46 down vote accepted
+50

Rsync's filter rules can seem daunting when you read the manual, but there are a few simple principles that suffice in many cases:

  • Inclusions and exclusions:

    • Excluding files by name or by location is easy: --exclude=*~, --exclude=/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.
    • 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/
share|improve this answer
    
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
    
How important is the order of the filters? –  Seamus Sep 29 '10 at 12:38
2  
@Seamus: Filter order is very important: the first matching rule applies. –  Gilles Sep 29 '10 at 17:30
    
Hint appreciated, but your filter order fails me! At least in my environment (rsync 3.0.9 on Mac OS X 10.6.8). As of 2014-03-17 @jmanning2k has the best answer, as it solves the original posters inquiry exactly, reproducible within my environment! –  porg Mar 17 at 23:46
    
@porg I don't get it: what's the problem? jmanning2k's answer has exactly the same filter as me, except for the order of the two include rules which doesn't matter. –  Gilles Mar 18 at 0:07
show 3 more comments
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 '*'.

share|improve this answer
    
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 at 23:38
add comment

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.

share|improve this answer
    
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
1  
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
add comment

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.

share|improve this answer
    
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
add comment

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/
share|improve this answer
    
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
    
I was worried that this solution wouldn't work if you had some pdfs in foo/ and some in foo/bar/ and you wanted them all moved that wouldn't work, but it does work. I like this answer, but I do actually want to preserve folder structure as well, so this doesn't work. But I do like the zsh ** trick, and I'm sure I'll use it in the future! –  Seamus Sep 29 '10 at 12:37
1  
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
add comment

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).

share|improve this answer
    
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
1  
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
5  
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
add comment

How about this:

rsync -avn --include="*.pdf" ~/Latex/ ~/Output/
share|improve this answer
    
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
1  
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
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.