在目录中查找并删除重复项


12

我的目录中包含多个img文件,其中一些是相同的,但它们的名称不同。我需要删除重复项,但仅使用bash脚本就不需要外部工具。我是Linux的初学者。我尝试了嵌套的for循环来比较md5总和,并根据结果将其删除,但是语法出了点问题,因此不起作用。有什么帮助吗?

我试过的是...

for i in directory_path; do
    sum1='find $i -type f -iname "*.jpg" -exec md5sum '{}' \;'
    for j in directory_path; do
        sum2='find $j -type f -iname "*.jpg" -exec md5sum '{}' \;'
        if test $sum1=$sum2 ; then rm $j ; fi
    done
done

我得到: test: too many arguments


请同时附上您在问题中遇到的任何错误消息。
terdon

为什么不能使用fdupes等外部工具?@terdon的答案是惊人的,但它确实突显了为什么尽可能使用一种好的工具。如果它是某种专用的硬件或服务器,您仍然可以从具有fdupes之类工具的计算机上通过网络等访问它。

Answers:


28

您的脚本中有很多问题。

  • 首先,为了将命令的结果分配给变量,您需要将其用反斜线(`command`)或最好用括起来$(command)。您使用单引号('command')将其括起来,而不是将命令的结果分配给变量,而是将命令本身分配为字符串。因此,您test实际上是:

    $ echo "test $sum1=$sum2"
    test find $i -type f -iname "*.jpg" -exec md5sum {} \;=find $j -type f -iname "*.jpg" -exec md5sum {} \;
  • 下一个问题是该命令md5sum返回的不仅是哈希值:

    $ md5sum /etc/fstab
    46f065563c9e88143fa6fb4d3e42a252  /etc/fstab

    您只想比较第一个字段,因此您应该md5sum通过仅输出第一个字段的命令来解析输出:

    find $i -type f -iname "*.png" -exec md5sum '{}' \; | cut -f 1 -d ' '

    要么

    find $i -type f -iname "*.png" -exec md5sum '{}' \; | awk '{print $1}' 
  • 同样,该find命令将返回许多匹配项,而不仅仅是一个匹配项,并且每个匹配项都将被第二个重复find。这意味着您将在某个时候将同一个文件与其自身进行比较,md5sum将是相同的,最终您将删除所有文件(我在包含a.jpg和的测试目录上运行了该文件b.jpg):

    for i in $(find . -iname "*.jpg"); do
      for j in $(find . -iname "*.jpg"); do
         echo "i is: $i and j is: $j"
      done
    done   
    i is: ./a.jpg and j is: ./a.jpg   ## BAD, will delete a.jpg
    i is: ./a.jpg and j is: ./b.jpg
    i is: ./b.jpg and j is: ./a.jpg
    i is: ./b.jpg and j is: ./b.jpg   ## BAD will delete b.jpg
  • for i in directory_path除非要传递目录数组,否则您不希望运行。如果所有这些文件都在同一目录中,则要运行for i in $(find directory_path -iname "*.jpg")以遍历所有文件。

  • 在find的输出中使用循环是一个坏主意for。您应该使用while循环或遍历

    find . -iname "*.jpg" | while read i; do [...] ; done

    或者,如果所有文件都位于同一目录中:

    for i in *jpg; do [...]; done

    根据您的shell和设置的选项,即使对于子目录中的文件,也可以使用globing,但在此不做介绍。

  • 最后,还应该引用变量,否则带空格的目录路径将破坏脚本。

文件名可以包含空格,换行,反斜杠和其他奇怪的字符,要在while循环中正确处理这些文件,您需要添加更多选项。您要编写的内容如下:

find dir_path -type f -iname "*.jpg" -print0 | while IFS= read -r -d '' i; do
  find dir_path -type f -iname "*.jpg" -print0 | while IFS= read -r -d '' j; do
    if [ "$i" != "$j" ]
    then
      sum1=$(md5sum "$i" | cut -f 1 -d ' ' )
      sum2=$(md5sum "$j" | cut -f 1 -d ' ' )
      [ "$sum1" = "$sum2" ] && rm "$j"
    fi
  done
done

一个更简单的方法是:

find directory_path -name "*.jpg" -exec md5sum '{}' + | 
 perl -ane '$k{$F[0]}++; system("rm $F[1]") if $k{$F[0]}>1'

更好的版本可以处理文件名中的空格:

find directory_path -name "*.jpg" -exec md5sum '{}' + | 
 perl -ane '$k{$F[0]}++; system("rm \"@F[1 .. $#F]\"") if $k{$F[0]}>1'

这个小的Perl脚本将贯穿find命令的结果(即md5sum和文件名)运行。在-a供选择perl的空白拆分输入线并保存它们中F的数组,所以$F[0]将md5sum也和$F[1]文件名。md5sum保存在哈希中k,脚本检查哈希是否已经被看到(if $k{$F[0]}>1),并在文件已删除()的情况下删除该文件system("rm $F[1]")


虽然可以正常工作,但是对于大型图像集合来说将非常慢,并且您无法选择保留哪些文件。有许多程序可以更优雅地处理此问题,包括:


为Perl代码段+1。真的很优雅!您也可以使用Perl自己unlinksystem电话来代替打电话。
Joseph R.

@JosephR。谢谢 :)。虽然有一个错误,但对于带有空格的文件名,它会失败,因为只有名称中的第一个字符(直到第一个空格)才会在其中$F[1]。使用数组切片修复了它。至于unlink(),我知道,但是想要将Perlisms降到最低,如果您不了解Perl,则更容易理解系统调用。
terdon

13

有一个漂亮的程序称为fdupes,它可以简化整个过程并提示用户删除重复项。我认为值得检查:

$ fdupes --delete DIRECTORY_WITH_DUPLICATES
[1] DIRECTORY_WITH_DUPLICATES/package-0.1-linux.tar.gz        
[2] DIRECTORY_WITH_DUPLICATES/package-0.1-linux.tar.gz.1

Set 1 of 1, preserve files [1 - 2, all]: 1

   [+] DIRECTORY_WITH_DUPLICATES/package-0.1-linux.tar.gz
   [-] DIRECTORY_WITH_DUPLICATES/package-0.1-linux.tar.gz.1

基本上,它提示我要保留哪个文件,我键入1,然后删除了第二个。

其他有趣的选项是:

-r --recurse
    for every directory given follow subdirectories encountered within

-N --noprompt
    when used together with --delete, preserve the first file in each set of duplicates and delete the others without prompting the user

从您的示例中,您可能希望将其运行为:

fdupes --recurse --delete --noprompt DIRECTORY_WITH_DUPLICATES

请参阅man fdupes以获取所有可用选项。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.