递归地比较目录,忽略所有二进制文件。

82

我正在使用 Fedora Constantine 操作系统,想要递归地比较两个目录以检查源代码的更改。由于该项目的设置(在我参与之前!叹气),这些目录包含源代码、二进制文件以及大型二进制数据集。尽管最终可以通过diff来比较这些目录,但如果我可以忽略二进制文件则需要大约20秒钟。

据我所知,diff没有“忽略二进制文件”的模式,但它有一个忽略参数,可以忽略文件中的正则表达式匹配项。我不知道该写什么来忽略二进制文件,无论其扩展名是什么。

我正在使用以下命令,但它不会忽略二进制文件。有人知道如何修改此命令以实现此功能吗?

diff -rq dir1 dir2

2
尝试使用 cmp 而不是 diff,虽然不会忽略二进制文件,但速度应该更快。 - Fredrik Pihl
2
哎呀,这是源代码控制的典型案例。如果你还没有使用它,那么你应该开始使用了。如果决策不在你手中,你应该激烈地争辩。只要有一个适当的Git设置,你的问题就会消失... - fearlesstost
6
相信我,我知道。我正在进行本科研究,这个项目的设置不太对。使用CVS/SVN/GIT可以解决这个问题。 你知道什么比这更糟糕吗?我被分配到一个没有或很少文档的Fortran项目上。在这个目录中有8个版本的项目,每个版本都有不同的makefile(几乎)做同样的事情。相信我,我正在尽我所能与我的监督者争论。 - Zéychin
@FredrikPihl 我认为cmp不支持目录,更不用说递归了。它在10年前支持目录吗? - Darren Ng
6个回答

69

有点作弊,但这是我使用的代码:

diff -r dir1/ dir2/ | sed '/Binary\ files\ /d' >outputfile

这个函数递归地比较了dir1和dir2的内容,使用sed命令删除了二进制文件(即以"Binary files "开头的行),然后将结果重定向到输出文件中。


7
您可以使用“-x”标志来排除文件。尝试使用命令diff -r -x '*.xml' dir1 dir2。另外,更多信息请查看man diff - xdhmoore
1
如果您的系统语言不同,请用适当的词替换Binary\ files\。它应该是您的语言中的第一个或两个词。在德语中是 Binärdateien\ - kap
1
@xdhmoore 感谢您的评论!此外,-x也是可重复的,如果要排除_多个_模式,可以这样做。例如 -x '*.ext1' -x '*.ext2' -x 'ext3' - Vasan
使用 sed 与仅使用 grep -v 'Binary files' 相比有何优势? - bluenote10
@bluenote10 是的,我认为在这种情况下使用grep -v更合适。 - Pierre

33

也许可以使用 grep -I(等同于 grep --binary-files=without-match)作为过滤器来筛选出二进制文件。

dir1='folder-1'
dir2='folder-2'
IFS=$'\n'
for file in $(grep -Ilsr -m 1 '.' "$dir1"); do
   diff -q "$file" "${file/${dir1}/${dir2}}"
done

这看起来非常有前途。我会检查一下并让您知道进展情况 / 如果成功的话,将其作为答案接受! - Zéychin
2
有人知道 IFS=$'\n' 的目的是什么吗? - Zubin
5
这是一个 Bash 内部变量。请在 http://tldp.org/LDP/abs/html/internalvariables.html 查找 IFS 的精确定义和行为。 - Harsh J
1
@Zubin IFS 意味着内部字段分隔符,用于通过在 IFS 值处拆分字符串来创建数组。 - Be Wake Pandey
1
@Zubin:请查看在Bash中何时将IFS设置为换行符? - codeforester

14

我来到这个(旧的)问题,寻找类似的东西(在传统的生产服务器上与默认的Apache安装相比的配置文件)。根据@fearlesstost在评论中的建议,git足够轻便快速,它可能比上面任何建议都更加简单明了。将version1复制到一个新目录。 然后执行:

git init
git add .
git commit -m 'Version 1'

现在,请删除此目录中版本 1 的所有文件,并将版本 2 复制到该目录中。现在执行以下操作:

git add .
git commit -m 'Version 2'
git show

这将向您展示Git在第一个提交和第二个提交之间所有差异的版本。对于二进制文件,它只会说它们不同。或者,您可以为每个版本创建一个分支,并尝试使用git的合并工具将它们合并。


8
如果您的项目中二进制文件的名称遵循特定的模式(如*.o*.so等),您可以将这些模式放在一个文件中,并使用-X(连字符 X)指定它。 exclude_file的内容。
*.o
*.so
*.git

命令:

diff -X exclude_file -r . other_tree > my_diff_file

更新:

现在可以使用-x代替-X,在命令行上指定排除模式而不是在文件中指定:

diff -r -x *.o -x *.so -x *.git dir1 dir2

1
它是 -x 而不是 -X。 - dpaks
2
@code_dweller 两者都存在:-x 用于在命令行中排除模式,而 -X 则表示包含所有要排除的模式的文件。 - simlev
答案中给出的最后一个命令应该在星号周围加上引号,否则 shell 将根据当前目录中存在的文件(在调用 diff 之前)扩展它们。因此,命令应该写成 diff -rx '*.o' -x '*.so' -x '*.git' dir1 dir2 - frougon

0

使用findfile命令的组合。这需要您对目录中file命令的输出进行一些研究;在下面的假设中,我假设您要比较的文件被报告为ASCII码。或者,使用grep -v过滤掉二进制文件。

#!/bin/bash

dir1=/path/to/first/folder
dir2=/path/to/second/folder

cd $dir1
files=$(find . -type f -print | xargs file | grep ASCII | cut -d: -f1)

for i in $files;
do
    echo diffing $i ---- $dir2/$i
    diff -q $i $dir2/$i
done

由于您可能已经知道巨大二进制文件的名称,因此请将它们放在哈希数组中,只有在文件不在哈希中时才执行差异操作,类似于这样:

#!/bin/bash

dir1=/path/to/first/directory
dir2=/path/to/second/directory

content_dir1=$(mktemp)
content_dir2=$(mktemp)

$(cd $dir1 && find . -type f -print > $content_dir1)
$(cd $dir2 && find . -type f -print > $content_dir2)

echo Files that only exist in one of the paths
echo -----------------------------------------
diff $content_dir1 $content_dir2    

#Files 2 Ignore
declare -A F2I
F2I=( [sqlite3]=1 [binfile2]=1 )

while read f;
do
    b=$(basename $f)
    if ! [[ ${F2I[$b]} ]]; then
        diff $dir1/$f $dir2/$f
    fi
done < $content_dir1

0

好的,作为一种简单的检查,您可以忽略与 /\0/ 相匹配的文件。


1
问题在于,它似乎根本不支持忽略文件。 - Zéychin
2
“-x”标志可以用于忽略文件。 - xdhmoore

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接