27

How can you perform a recursive diff of the files in two directories (a and b):

$ diff -r a b 

but only look at files whose name matches a given pattern. For example, using the same syntax available in the find command, this would look like:

$ diff -r a b -name "*crazy*" 

which would show diffs between files with the same name and path in a and b, which have "crazy" in their name.

Effectively, I'm looking for the opposite of the --exclude option which is available in diff.

1
  • I agree that this would be useful. grep has an --include flag Commented Apr 3, 2013 at 20:08

2 Answers 2

22

Perhaps this is a bit indirect, but it ought to work. You can use find to get a list of files that don't match the pattern, and then "exclude" all those files:

find a b -type f ! -name 'crazy' -printf '%f\n' | diff -r a b -X - 

The -X - will make diff read the patterns from stdin and exclude anything that matches. This should work provided your files don't have funny chars like * or ? in their names. The only downside is that your diff won't include the find command, so the listed diff command is not that useful.

(I've only tested it with GNU find and diff).

EDIT:

Since only non-GNU find doesn't have -printf, sed could be used as an alternative:

find a b -type f ! -name '*crazy*' -print | sed -e 's|.*/||' | diff -X - -r a b 

That's also assuming that non-GNU diff has -X which I don't know.

Sign up to request clarification or add additional context in comments.

5 Comments

Unfortunately, this has to work on Mac OS X, and I see from stackoverflow.com/questions/752818/… that BSD's find doesn't have a -printf option. So there's apparently no nice to way to make a BSD and GNU compatible command.
@EdwardD'Souza Consider MacPorts if you want to get GNU tools on OSX.
Thanks @FatalError; could you please edit the first part of your command so that it only passes in files in 'a' and 'b', and not all files in the current in the directory? Something like: ( (cd a && find . -type f ! -name *crazy* -print) && (cd b && find . -type f ! -name *crazy* -print) )
@EdwardD'Souza Sure thing. I updated it with what I think should work (once again I'm really only familiar with the GNU flavor of the tools). If that doesn't work, I will update it to your suggestion.
@FatalError I'm unclear as to what "your diff won't include the find command" means. You are saying that files with the name "find" will be excluded because the word "find" is piped to the -X? Am I correct?
2

I couldn't make it work with -X -, so I used a different approach - let find find the files recursively according to my requirements and let diff compare individual files:

a="/your/dir" b="/your/other/dir" for f in $(find $a -name "*crazy*" | grep -oP "^$a/\K.*"); do diff -u $a/$f $b/$f done 

Or, in one line:

a="/your/dir" && b="/your/other/dir" && for f in $(find $a -name "*crazy*" | grep -oP "^$a/\K.*"); do diff -u $a/$f $b/$f; done 

This grep -oP "^$a/\K.*" is used to extract the path relative to the directory a, i.e. it removes /your/dir/ from /your/dir/and/a/file, to make it and/a/file.

1 Comment

A quick suggestion to avoid the grep, you can get the file path relative to the folder you ran find against with simple bash substitution: FILE=${f:${#a}+1} ${#a} gives the length of the $a variable. We add one to trim the trailing slash. ${f:n} removes the first "n" characters.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.