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

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

我有一个包含多个 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

答案1

您的脚本中有很多问题。

  • 首先,为了分配结果将命令传递给变量时,您需要将其括在反引号 ( `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") 来遍历所有文件。

  • 这是一个坏主意for对 find 的输出使用循环。你应该使用while循环或通配:

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

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

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

    根据您的 shell 和您设置的选项,您甚至可以对子目录中的文件使用通配符,但我们在这里不讨论这一点。

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

文件名可以包含空格、换行符、反斜杠和其他奇怪的字符,为了在循环中正确处理这些字符,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数组中,md5sum 和文件名也将如此。 md5sum 保存在散列中,脚本检查散列是否已被看到 ( ),如果有则删除文件 ( )。perlF$F[0]$F[1]kif $k{$F[0]}>1system("rm $F[1]")


虽然这可行,但对于大型图像集合来说,速度会非常慢,而且您无法选择要保留哪些文件。有许多程序可以以更优雅的方式处理此问题,包括:

答案2

有一个名为的漂亮程序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所有可用选项。

相关内容