EDIT: Show first the final solution
This one handles spaces in filenames and lets you skip specific paths
The trick to skip a path is to search for " and $NEWER/foo", where $NEWER variable contains the second directory given to the diff command
diff -rqN $OLDER $NEWER | \
sed 's/^Files //;s/ differ$//;' | \
grep -v \
-e "and $NEWER/data/" \
-e "and $NEWER/modules_v4/" \
-e ".*-[bB][aA][kK]-" \
-e "-[oO][rR][iI][gG][0-9]*" | \
awk -F" and " '{
tmp=$0;
s="";
num=gsub(" and ", "", tmp);
ocu=(num/2); ocu=(sprintf("%d",ocu+=ocu<0?0:0.9));
for(i=1;i<=ocu;++i) s = s (s=="" ? "" : FS) $i;
print substr(s, 1) }' | \
cut -d'/' -f2- | \
xargs -I{} diff -uN $OLDER/{} $NEWER/{} > $ARCH
Now an explanation:
In $OLDER and NEWER variables are stored the directories to compare
1 diff -rqN $OLDER $NEWER | \ Gets a list of changed files
2 sed 's/^Files //;s/ differ$//;' | \ Removes prefix and suffix added by diff
3 grep -v \ Remove lines that match (-v)
4 -e "and $NEWER/data/" \ Remove a specific path (directory named data)
5 -e "and $NEWER/modules_v4/" \ Remove a specific path (directory name modules_v4
6 -e ".*-[bB][aA][kK]-" \ Case insensitive file names containing -bak-
7 -e "-[oO][rR][iI][gG][0-9]*" | \ Case insensitive file names containing something like -orig4 (with or without a number at the end)
8 awk -F" and " '{ AWK to extract file name and handle spaces.
The diff lines contain something like "filename1 and filename2"
It handles filenames containing " and " without a problem
9 tmp=$0;
10 s="";
11 num=gsub(" and ", "", tmp); Counts number of times " and " appear
12 ocu=(num/2); ocu=(sprintf("%d",ocu+=ocu<0?0:0.9)); ceil(Middle)
13 for(i=1;i<=ocu;++i) s = s (s=="" ? "" : FS) $i; finds the position of middle appearance of " and " (without it)
14 print substr(s, 1) }' | \ Substring from beginning to middle appearance of " and "
15 cut -d'/' -f2- | \ Remove first directory from path
16 xargs -I{} diff -uN $OLDER/{} $NEWER/{} > $ARCH
About the answer given by @wuan
$ diff -rq foo.orig foo | grep -vP 'ignore1/|exclude2/' | awk '{print $2}' | cut -d'/' -f2- | xargs -I{} diff -u foo.orig/{} foo/{}
What this solution does is:
- Generate a list of file names with changes
- Filter that list of filenames using
grep
. In this case shows all files not matching a Perl regular expression (-vP
)
- Take the second filename
- Removing
foo
directory at the beginning of the path
- Execute diff showing contents.
In my opinion is a good idea.
NOTE1: It doesn't handle file names with spaces.
NOTE2: It doesn't handle well cases where there is a file in one directory that does not exist in the other.
I would just modify it a bit to fix this (the "N" flag in the first diff):
$ diff -rqN foo.orig foo | grep -vP 'ignore1/|exclude2/' | awk '{print $2}' | cut -d'/' -f2- | xargs -I{} diff -u foo.orig/{} foo/{}
Or, if you want to include contents of files non existent in the other directory, could use -N
option or --unidirectional-new-file
option in the second diff:
$ diff -rqN foo.orig foo | grep -vP 'ignore1/|exclude2/' | awk '{print $2}' | cut -d'/' -f2- | xargs -I{} diff -uN foo.orig/{} foo/{}
or
$ diff -rqN foo.orig foo | grep -vP 'ignore1/|exclude2/' | awk '{print $2}' | cut -d'/' -f2- | xargs -I{} diff -u --unidirectional-new-file foo.orig/{} foo/{}
Taken from this web page
If the older directory contains large files that are not in the newer
directory, you can make the patch smaller by using the
--unidirectional-new-file option instead of -N. This option is like -N except that it inserts the contents only of files that appear in the
second directory but not the first (that is, files that were added)
diff
's--exclude
option is so shitty...