lin*_*gin 12 bash shell-script
我有一个包含多个 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
Run Code Online (Sandbox Code Playgroud)
我得到: test: too many arguments
ter*_*don 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 {} \;
Run Code Online (Sandbox Code Playgroud)下一个问题是该命令md5sum
返回的不仅仅是哈希值:
$ md5sum /etc/fstab
46f065563c9e88143fa6fb4d3e42a252 /etc/fstab
Run Code Online (Sandbox Code Playgroud)
您只想比较第一个字段,因此您应该md5sum
通过将输出传递给仅打印第一个字段的命令来解析输出:
find $i -type f -iname "*.png" -exec md5sum '{}' \; | cut -f 1 -d ' '
Run Code Online (Sandbox Code Playgroud)
或者
find $i -type f -iname "*.png" -exec md5sum '{}' \; | awk '{print $1}'
Run Code Online (Sandbox Code Playgroud)此外,该find
命令将返回许多匹配项,而不仅仅是一个,并且这些匹配项中的每一个都将被第二个匹配find
。这意味着在某些时候您会将同一个文件与其自身进行比较,md5sum 将是相同的,您最终将删除所有文件(我在包含a.jpg
and的测试目录上运行此文件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
Run Code Online (Sandbox Code Playgroud)for i in directory_path
除非您传递目录数组,否则您不想运行。如果所有这些文件都在同一个目录中,您需要运行for i in $(find directory_path -iname "*.jpg"
) 来查看所有文件。
在 find 的输出中使用循环是一个坏主意for
。您应该使用while
循环或通配:
find . -iname "*.jpg" | while read i; do [...] ; done
Run Code Online (Sandbox Code Playgroud)
或者,如果您的所有文件都在同一目录中:
for i in *jpg; do [...]; done
Run Code Online (Sandbox Code Playgroud)
根据您的 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
Run Code Online (Sandbox Code Playgroud)
更简单的方法是:
find directory_path -name "*.jpg" -exec md5sum '{}' + |
perl -ane '$k{$F[0]}++; system("rm $F[1]") if $k{$F[0]}>1'
Run Code Online (Sandbox Code Playgroud)
可以处理文件名中的空格的更好版本:
find directory_path -name "*.jpg" -exec md5sum '{}' + |
perl -ane '$k{$F[0]}++; system("rm \"@F[1 .. $#F]\"") if $k{$F[0]}>1'
Run Code Online (Sandbox Code Playgroud)
这个小的 Perl 脚本将运行find
命令的结果(即 md5sum 和文件名)。在-a
供选择perl
的空白拆分输入线并保存它们中F
的数组,所以$F[0]
将md5sum也和$F[1]
文件名。md5sum 保存在散列中k
,脚本检查散列是否已经被看到 ( if $k{$F[0]}>1
),如果有 ( system("rm $F[1]")
)则删除文件。
虽然这会起作用,但对于大型图像集合来说会非常慢,而且您无法选择要保留哪些文件。有许多程序以更优雅的方式处理这个问题,包括:
Ter*_*ior 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
Run Code Online (Sandbox Code Playgroud)
基本上,它促使我对哪个文件来保存,我输入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
Run Code Online (Sandbox Code Playgroud)
从您的示例中,您可能希望将其运行为:
fdupes --recurse --delete --noprompt DIRECTORY_WITH_DUPLICATES
Run Code Online (Sandbox Code Playgroud)
查看man fdupes
所有可用选项。