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

23

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

#!/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 " 

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

8个回答

25

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

解决方案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命令的整个输出保存为变量的列表?


你可以使用grep -f来摆脱while循环并简化它:cat $tempfile | sed 's_.*/__' | sort | uniq -d| grep -f $tempfile - A. Wilson
1
解决方案1中的小错误可能会导致误报。最好将最后一个find写成:find $dirname -type f | grep "^${fileName}$" - prinzdezibel
我该如何修改解决方案2,以便第一个找到的文件不会被添加到临时文件中,只有第二次发现的重复文件会被添加? - user3746428
MacOs:find:-printf:未知的主要或运算符。 - Charaf

23

是的,这确实是一个非常老的问题。 但是所有这些循环和临时文件似乎有点麻烦。

这是我的一行回答:

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

此外,在sortuniq中有选项可以忽略大小写(正如主题开启者通过管道传递tr来实现的)。使用man uniqman sort查找它们。


/usr/share/fslint/fslint/findsn /path/to/files但我喜欢你的一行代码更灵活。 - Linulin

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}}}  

没有使用awk的可能吗?无论如何,谢谢 :) - yak
你可以使用任何支持关联数组(或哈希是另一个名称)的编程语言来实现 - Perl 就是一个例子。Bash 4 也支持关联数组。 - jim mcnamara
所以你说只用bash的解决方案不可能?我的意思是,没有sed、awk、perl、python等,只用纯bash? - yak
2
顺便说一下,这个解决方案只会告诉你文件名,而不会告诉你它们所在的路径。我认为这是一个要求。 - Elisiano Petrini
@ElisianoPetrini:哎呀,谢谢,你说得对。我需要一个完整的路径。问题再次开放。 - yak

2
仅使用一个“find”命令:
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

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

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

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

以下是命名为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,版本4.1.2(1)-release(x86_64-redhat-linux-gnu)


0

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

使用 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

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