如何在Unix/Linux中快速判断两个文件是否具有相同的内容?

350

我有一个shell脚本,需要检查两个文件是否包含相同的数据。我要对很多文件进行这样的检查,在我的脚本中,diff命令似乎是性能瓶颈。

以下是该行代码:

diff -q $dst $new > /dev/null

if ($status) then ...

有没有更快的比较文件的方法,也许是使用自定义算法而不是默认的 diff


16
这只是一些小问题,但你不是在询问两个文件是否相同,而是在询问它们的内容是否完全相同。相同的文件具有相同的inode(和相同的设备)。 - Zano
2
与被采纳的答案不同,此答案中的测量并不认为 diffcmp 之间有任何显着差异。 - wedi
10个回答

554
我相信cmp会在第一个字节的差异处停止。
cmp --silent $old $new || echo "files are different"

2
我怎样才能添加多个命令而不只是一个?我想要复制一个文件并重新启动。 - feedc0de
30
cmp -s $old $new 也可以使用。 -s--silent 的缩写。 - tim-phillips
9
作为加速比较的一种方法,您应该在比较内容之前检查文件大小是否相等。有人知道 cmp 命令是否会执行此操作吗? - BeowulfNode42
5
要运行多个命令,您可以使用括号: cmp -s old new || { echo not; echo the; echo same; } - unfa
16
是的,任何一个合理的cmp实现都会首先检查文件大小。这是 GNU 版本的实现,如果您想查看它包含的其他优化,请访问以下链接:http://git.savannah.gnu.org/cgit/diffutils.git/tree/src/cmp.c - Ryan Graham
显示剩余7条评论

81

我像@Alex Howansky一样使用了'cmp --silent'。但是我需要正面和负面的响应,所以我使用:

cmp --silent file1 file2 && echo '### SUCCESS: Files Are Identical! ###' || echo '### WARNING: Files Are Different! ###'

我可以在终端或通过ssh运行此命令,以检查文件是否与一个不变的文件相同。


23
如果你的 echo success 命令(或者替代它的其他命令)失败了,你的 "negative response" 命令将会被执行。你应该使用一个 "if-then-else-fi" 结构体。例如,像 这个简单的例子 - Wildcard

47

快速而安全地比较任意两个文件:

if cmp --silent -- "$FILE1" "$FILE2"; then
  echo "files contents are identical"
else
  echo "files differ"
fi

它易读、高效,并适用于任何文件名,包括"` $()


21
由于我的声望不够,无法将此信息作为评论添加。但是,如果您要使用cmp命令(并且不需要/不想冗长),则可以直接获取退出状态。根据cmp手册的描述:
如果文件是“-”或缺失,则读取标准输入。如果输入相同,则退出状态为0,如果不同,则为1,如果有问题,则为2。
因此,您可以执行以下操作:
STATUS="$(cmp --silent $FILE1 $FILE2; echo $?)"  # "$?" gives exit status for each comparison

if [[ $STATUS -ne 0 ]]; then  # if status isn't equal to 0, then execute code
    DO A COMMAND ON $FILE1
else
    DO SOMETHING ELSE
fi

编辑:感谢大家的评论!我在这里更新了测试语法。然而,如果您正在寻找与此答案类似的可读性、风格和语法,我建议您使用Vasili的答案。


是的,但这实际上是一种更复杂的做法cmp --silent $FILE1 $FILE2 ; if [ "$?" == "1" ]; then echo "files differ"; fi,而这又是一种更复杂的做法cmp --silent $FILE1 $FILE2 || echo "files differ",因为您可以直接在表达式中使用命令。它代替了$?。因此,将比较命令的存在状态。这就是其他答案所做的。顺便说一下,如果有人遇到了“-silent”,则不是所有地方都支持它(忙碌盒)。使用“-s”。 - papo
3
这可以简化为只有 if cmp --silent -- "$FILE1" "$FILE2"; then ... else ... fi - VasiliNovikov
正如@VasiliNovikov所指出的那样,您可以只需执行if command; then ... else ... fi此外,@Gregory您的代码存在常见的bash陷阱。 [[实际上是bash语法,应该按照以下方式进行:if [[ ... ]](注意空格)一个非常好的URL,可了解常见的bash陷阱:https://mywiki.wooledge.org/BashPitfalls - Chevraut
@Chevraut 在重新阅读了这个问答后,注意到所有当前的建议都不是完全安全的情况下,我创建了自己的答案(基本上与我在评论中写的一样)。 - VasiliNovikov

6
你可以使用校验和算法(如sha256)进行比较。
sha256sum oldFile > oldFile.sha256

echo "$(cat oldFile.sha256) newFile" | sha256sum --check

newFile: OK

如果文件是不同的,结果将会是这样
newFile: FAILED
sha256sum: WARNING: 1 computed checksum did NOT match

不需要将哈希值保存在单独的文件中,只需计算它们并进行检查:echo "$(sha256sum "$1" | sed 's/ .*//') $2" | sha256sum --check 1>/dev/null - undefined

3
对于相同的文件,任何方法都需要完全读取两个文件,即使已经在过去读取过。
没有其他选择。因此,在某个时间点创建哈希或校验和需要读取整个文件。大文件需要时间。
文件元数据检索比读取大文件要快得多。
那么,有哪些文件元数据可以用来确定文件是否不同呢?例如文件大小? 或者甚至是file命令的结果,该命令只读取文件的一小部分?
文件大小示例代码片段:
  ls -l $1 $2 | 
  awk 'NR==1{a=$5} NR==2{b=$5} 
       END{val=(a==b)?0 :1; exit( val) }'

[ $? -eq 0 ] && echo 'same' || echo 'different'  

如果文件大小相同,则只能进行完整文件读取。


1
使用 ls -n 命令来避免用户或组名中有空格的问题。 - tricasse

1

也尝试使用cksum命令:

chk1=`cksum <file1> | awk -F" " '{print $1}'`
chk2=`cksum <file2> | awk -F" " '{print $1}'`

if [ $chk1 -eq $chk2 ]
then
  echo "File is identical"
else
  echo "File is not identical"
fi

cksum命令将输出文件的字节数。请参见'man cksum'。


3
这也是我的第一反应。然而,如果你需要多次比较同一个文件,哈希值就很有意义,因为哈希值只需计算一次。如果你只需要比较一次,那么 md5 无论如何都会读取整个文件,所以 cmp 在找到第一个不同点后停止,速度会更快。 - Francesco Dondi

1
在使用树莓派3B+进行一些测试时(我正在使用叠加文件系统,并需要定期同步),我对diff -q和cmp -s进行了比较;请注意,这是来自/dev/shm内部的日志,因此磁盘访问速度不是问题。
[root@mypi shm]# dd if=/dev/urandom of=test.file bs=1M count=100 ; time diff -q test.file test.copy && echo diff true || echo diff false ; time cmp -s test.file test.copy && echo cmp true || echo cmp false ; cp -a test.file test.copy ; time diff -q test.file test.copy && echo diff true || echo diff false; time cmp -s test.file test.copy && echo cmp true || echo cmp false
100+0 records in
100+0 records out
104857600 bytes (105 MB) copied, 6.2564 s, 16.8 MB/s
Files test.file and test.copy differ

real    0m0.008s
user    0m0.008s
sys     0m0.000s
diff false

real    0m0.009s
user    0m0.007s
sys     0m0.001s
cmp false
cp: overwrite âtest.copyâ? y

real    0m0.966s
user    0m0.447s
sys     0m0.518s
diff true

real    0m0.785s
user    0m0.211s
sys     0m0.573s
cmp true
[root@mypi shm]# pico /root/rwbscripts/utils/squish.sh

我运行了几次。在我使用的测试机上,cmp -s始终具有稍短的时间。所以如果你想在两个文件之间使用cmp -s来做一些事情...
identical (){
  echo "$1" and "$2" are the same.
  echo This is a function, you can put whatever you want in here.
}
different () {
  echo "$1" and "$2" are different.
  echo This is a function, you can put whatever you want in here, too.
}
cmp -s "$FILEA" "$FILEB" && identical "$FILEA" "$FILEB" || different "$FILEA" "$FILEB"

0
你可以比较哈希值,例如SHA-256或MD5。
same-contents() {
    echo "$(sha256sum "$1" | sed 's/ .*//') $2" | sha256sum --check 1>/dev/null 2>&1
}
alias same-file="same-contents" # Technically the same "file" has the same inode

if same-contents file1.txt file2.txt; then 
    echo true
else
    echo false
fi

这基本上就是this答案中建议的内容,但他们通过先将哈希值存储在文件中,使事情变得过于复杂。这个sed命令会去掉文件名,只留下哈希值。
你可能仍然更喜欢使用cmp,因为它专为此而设计,可以逐字节比较,而哈希需要读取整个文件,但我觉得这是一个有趣的脚本。

0
如果你想要更多自定义的差异对比,可以使用git diff
if (git diff --no-index --quiet old.txt new.txt) then
  echo "files contents are identical"
else
  echo "files differ"
fi

--quiet
禁用程序的所有输出。意味着--exit-code。
--exit-code 使程序以类似于diff(1)的代码退出。也就是说,如果有差异,则以1退出,而0表示没有差异。
此外,还有各种算法和设置可供选择:[ref]
--diff-algorithm={patience|minimal|histogram|myers}
选择一个差异算法。变体如下:
默认、myers:基本的贪婪差异算法。目前为默认选项。
minimal:花费额外时间以确保生成最小可能的差异。 patience:在生成补丁时使用“耐心差异”算法。
histogram:该算法扩展了耐心算法以“支持低频共同元素”。

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