如何在给定目录中(递归地)查找重复的文件名?

问题描述 投票:0回答:9

我需要在给定的目录树中找到每个重复的文件名。我不知道用户将提供什么目录树作为脚本参数,所以我不知道目录层次结构。我试过这个:

#!/bin/sh
find -type f | while IFS= read vo
do
echo `basename "$vo"`
done

但这并不是我真正想要的。它只找到一个重复的文件,然后结束,即使有更多重复的文件名,它也不会打印整个路径(仅打印文件名)和重复计数。我想做一些类似于这个命令的事情:

find DIRNAME | tr '[A-Z]' '[a-z]' | sort | uniq -c | grep -v " 1 " 

但它对我不起作用,不知道为什么。即使我有重复项,它也不会打印任何内容。

bash shell duplicates filenames
9个回答
27
投票

这是另一个不使用 awk 的解决方案(基于 @jim-mcnamara 的建议):

解决方案1

#!/bin/sh 
dirname=/path/to/directory
find $dirname -type f | sed 's_.*/__' | sort|  uniq -d| 
while read fileName
do
find $dirname -type f | grep "$fileName"
done

但是,您必须执行相同的搜索两次。如果您必须搜索大量数据,这可能会变得非常慢。将“查找”结果保存在临时文件中可能会提供更好的性能。

解决方案2(使用临时文件)

#!/bin/sh 
dirname=/path/to/directory
tempfile=myTempfileName
find $dirname -type f  > $tempfile
cat $tempfile | sed 's_.*/__' | sort |  uniq -d| 
while read fileName
do
 grep "/$fileName" $tempfile
done
#rm -f $tempfile

由于在某些情况下您可能不想在硬盘上写入临时文件,因此您可以选择适合您需要的方法。 这两个示例都打印出文件的完整路径。

这里的额外问题:是否可以将 find 命令的整个输出作为列表保存到变量中?


26
投票

是的,这是一个非常老的问题。 但所有这些循环和临时文件看起来有点麻烦。

这是我的一行答案:

find /PATH/TO/FILES -type f -printf '%p/ %f\n' | sort -k2 | uniq -f1 --all-repeated=separate

由于

uniq
sort
,它有其局限性:

  • 文件名中没有空格(空格、制表符)(将被
    uniq
    sort
    解释为新字段)
  • 需要将文件名打印为由空格分隔的最后一个字段(
    uniq
    不支持仅比较1个字段并且字段分隔符不灵活)

但是由于

find -printf
,它的输出非常灵活,并且对我来说效果很好。似乎也是 @yak 最初试图实现的目标。

展示您对此的一些选项:

find  /PATH/TO/FILES -type f -printf 'size: %s bytes, modified at: %t, path: %h/, file name: %f\n' | sort -k15 | uniq -f14 --all-repeated=prepend

sort
uniq
中还有忽略大小写的选项(正如主题开场白旨在通过管道通过
tr
来实现)。使用
man uniq
man sort
查找它们。


8
投票
#!/bin/sh
dirname=/path/to/check
find $dirname -type f | 
while read vo
do
  echo `basename "$vo"`
done | awk '{arr[$0]++; next} END{for (i in arr){if(arr[i]>1){print i}}}  

2
投票
#!/bin/bash

file=`mktemp /tmp/duplicates.XXXXX` || { echo "Error creating tmp file"; exit 1; }
find $1 -type f |sort >  $file
awk -F/ '{print tolower($NF)}' $file |
        uniq -c|
        awk '$1>1 { sub(/^[[:space:]]+[[:digit:]]+[[:space:]]+/,""); print }'| 
        while read line;
                do grep -i "$line" $file;
        done

rm $file

它也适用于文件名中的空格。这是一个简单的测试(第一个参数是目录):

./duplicates.sh ./test
./test/2/INC 255286
./test/INC 255286

2
投票

仅一个“查找”命令:

lst=$( find . -type f )
echo "$lst" | rev | cut -f 1 -d/ | rev | sort -f | uniq -i | while read f; do
   names=$( echo "$lst" | grep -i -- "/$f$" )
   n=$( echo "$names" | wc -l )
   [ $n -gt 1 ] && echo -e "Duplicates found ($n):\n$names"
done

1
投票

最近偶然发现了这个有趣的案例。即使问题早已过时,也在这里分享我的解决方案。

使用 join ,无需 grep、awk、python、sed、perl 等。 :

#!/bin/sh
list=$(mktemp)
find PATH/TO/DIR/ -type f -printf '%f\t%p\n' | sort -f >$list
cut -d\^I -f1 <$list | uniq -d -i | join -i -t\^I - $list
rm $list

快速注释:

    上面命令中的
  • ^I 代表制表符。替换为实际命令。
  • 支持文件名中的空格。
  • 文件名不得包含制表符或换行符。
  • 性能看起来非常好。在包含数千个文件的大型目录树上进行了测试,结果几乎是即时的。
  • 比较不区分大小写。可以通过删除排序“-f”和 uniq+join“-i”选项来实现区分大小写。

示例:

目录树:

a/f1
a/f2
a/f3
b/f2
c/f2
c/f3

输出:

f2  a/f2
f2  b/f2
f2  c/f2
f3  a/f3
f3  c/f3

0
投票

此解决方案针对找到的每个唯一文件名将一个临时文件写入临时目录。 在临时文件中,我写入了第一次找到唯一文件名的路径,以便稍后输出。 因此,我创建了比其他人发布的解决方案更多的文件。 但是,这是我能理解的。

以下是脚本,名为

fndupe

#!/bin/bash

# Create a temp directory to contain placeholder files.
tmp_dir=`mktemp -d`

# Get paths of files to test from standard input.
while read p; do
  fname=$(basename "$p")
  tmp_path=$tmp_dir/$fname
  if [[ -e $tmp_path ]]; then
    q=`cat "$tmp_path"`
    echo "duplicate: $p"
    echo "    first: $q"
  else
    echo $p > "$tmp_path" 
  fi
done

exit

以下是使用脚本的示例。

$ find . -name '*.tif' | fndupe

以下是脚本发现重复文件名时的示例输出。

duplicate: a/b/extra/gobble.tif
    first: a/b/gobble.tif

使用 Bash 版本测试:

GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu)


0
投票

这是我的贡献(这只是搜索特定的文件类型,在本例中是 pdf),但它是递归执行的:

#!/usr/bin/env bash

find . -type f | while read filename; do
    filename=$(basename -- "$filename")
    extension="${filename##*.}"
    if [[ $extension == "pdf" ]]; then
        fileNameCount=`find . -iname "$filename" | wc -l`
        if [[ $fileNameCount -gt 1 ]]; then
            echo "File Name: $filename, count: $fileNameCount"
        fi
    fi
done

0
投票

也许最简单的解决方案是与

cksfv
开关一起使用
-R
,这最终将为位于特定路径中的每个文件递归计算 CRC。我们可以依靠
awk
sort
cut
命令来生成一个干净的结果文件。

第一步是使用以下语法

cksfv -R /parent/path/ > output.txt

cksfv
的输出将如下所示:

/parent/path/folder1/file1 E113452E
/parent/path/folder1/file2 GE133453
/parent/path/folder2/file1 A441292E
etc

一旦结果出现在

output.txt
中,我们就可以依靠每行末尾计算的 CRC32 值,使用
awk '{print $NF,$0}' output.txt | sort | cut -f2- -d' '
命令链查找任何重复项。

此时,我们应该将所有行/文件按照其 CRC32 值以及它们所在的路径进行排序。最后一步将是使用我们选择的任何方法来安排清理。

当然,在任何现代 Debian/Ubuntu 系统上部署

cksfv
软件包可以通过调用
sudo apt install cksfv

来完成

更现代(阅读:更快)的方法是通过

cksfv-rs
或访问
rust
 部署 
https://github.com/althonos/cksfv.rs

您可以通过阅读我在 Linux 杂志上的文章

cksfv
 来了解有关 
cksfv-rs
https://www.linux-magazine.com/Issues/2024/287/cksfv

的更多信息

干杯!

© www.soinside.com 2019 - 2024. All rights reserved.