如何查找具有相同名称但在Linux中同一目录中存在的不同情况下的重复文件?

时间:2010-01-21 12:14:47

标签: linux bash command-line find filesystems

如何返回名为duplicates的文件列表,即具有相同名称但在同一目录中存在的不同情况?

我不关心文件的内容。我只需要知道任何具有相同名称副本的文件的位置和名称。

示例重复:

/www/images/taxi.jpg
/www/images/Taxi.jpg

理想情况下,我需要从基本目录中递归搜索所有文件。在上面的示例中,它是/www/

11 个答案:

答案 0 :(得分:38)

另一个答案很棒,但我建议不要使用“相当可怕”的perl脚本

perl -pe 's!([^/]+)$!lc $1!e'

只会小写路径的文件名部分。

编辑1:实际上整个问题可以通过以下方式解决:

find . | perl -ne 's!([^/]+)$!lc $1!e; print if 1 == $seen{$_}++'

编辑3:我找到了一个使用sed,sort和uniq的解决方案,它也会打印出重复项,但它只有在文件名中没有空格时才有效:

find . |sed 's,\(.*\)/\(.*\)$,\1/\2\t\1/\L\2,'|sort|uniq -D -f 1|cut -f 1

编辑2:这是一个较长的脚本,将打印出名称,它采用stdin上的路径列表,由find给出。不是很优雅,但仍然:

#!/usr/bin/perl -w

use strict;
use warnings;

my %dup_series_per_dir;
while (<>) {
    my ($dir, $file) = m!(.*/)?([^/]+?)$!;
    push @{$dup_series_per_dir{$dir||'./'}{lc $file}}, $file;
}

for my $dir (sort keys %dup_series_per_dir) {
    my @all_dup_series_in_dir = grep { @{$_} > 1 } values %{$dup_series_per_dir{$dir}};
    for my $one_dup_series (@all_dup_series_in_dir) {
        print "$dir\{" . join(',', sort @{$one_dup_series}) . "}\n";
    }
}

答案 1 :(得分:35)

尝试:

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

简单,真的:-)管道是不是很棒的野兽?

ls -1为每行提供一个文件,tr '[A-Z]' '[a-z]'将全部大写转换为小写,sort对它们进行排序(令人惊讶的是足够),uniq -c删除后续出现的文件重复的行同时给你一个计数,最后,grep -v " 1 "删除那些计数为1的行。

当我在一个“重复”(我将qq复制到qQ)的目录中运行时,我得到:

2 qq

对于“此目录和每个子目录”版本,如果您想要特定的目录起点(ls -1是目录,则只需将find .替换为find DIRNAMEDIRNAME你想要使用的名字。)

这会返回(对我来说):

2 ./.gconf/system/gstreamer/0.10/audio/profiles/mp3
2 ./.gconf/system/gstreamer/0.10/audio/profiles/mp3/%gconf.xml
2 ./.gnome2/accels/blackjack
2 ./qq

由以下原因引起:

pax> ls -1d .gnome2/accels/[bB]* .gconf/system/gstreamer/0.10/audio/profiles/[mM]* [qQ]?
.gconf/system/gstreamer/0.10/audio/profiles/mp3
.gconf/system/gstreamer/0.10/audio/profiles/MP3
.gnome2/accels/blackjack
.gnome2/accels/Blackjack
qq
qQ

更新

实际上,在进一步反思时,tr将小写路径的所有组件,以便

/a/b/c
/a/B/c

将被视为重复,即使它们位于不同的目录

如果你只希望在一个目录中重复显示为匹配,你可以使用(相当怪异):

perl -ne '
    chomp;
    @flds = split (/\//);
    $lstf = $f[-1];
    $lstf =~ tr/A-Z/a-z/;
    for ($i =0; $i ne $#flds; $i++) {
        print "$f[$i]/";
    };
    print "$x\n";'

取代:

tr '[A-Z]' '[a-z]'

它的作用是仅仅小写路径名的最后部分而不是整个部分。此外,如果您只想要常规文件(没有目录,FIFO等),请使用find -type f来限制返回的内容。

答案 2 :(得分:5)

我相信

ls | sort -f | uniq -i -d

更简单,更快,并且会得到相同的结果

答案 3 :(得分:2)

跟进mpez0的响应,以递归方式检测,只需将“ls”替换为“find”。 我看到的唯一问题是,如果这是一个重复的目录,那么这个目录中的每个文件都有1个条目。需要一些人脑来治疗这种输出。

但无论如何,你不会自动删除这些文件,不是吗?

find . | sort -f | uniq -i -d

答案 4 :(得分:2)

如果您编译deb包不包含的findsn,这是一个名为fslint的小命令行应用程序。

它会找到任何具有相同名称的文件,它的闪电速度很快,可以处理不同的情况。

/findsn --help
find (files) with duplicate or conflicting names.
Usage: findsn [-A -c -C] [[-r] [-f] paths(s) ...]

如果没有提供参数,则会搜索$ PATH以查找任何冗余 或冲突的文件。

-A  reports all aliases (soft and hard links) to files.
    If no path(s) specified then the $PATH is searched.

如果只指定了路径,则检查它们是否有重复名称 文件。您可以使用-C来限定此值以忽略此搜索中的大小写。 使用-c进行限定更具限制性,因为只有文件(或目录) 在报告的情况下,名称不同的同一目录中。 I.E. -c将标记文件和如果转移将发生冲突的目录 到不区分大小写的文件系统。请注意,如果指定-c或-C,则 没有指定当前目录的路径。

答案 5 :(得分:2)

以下是如何查找所有重复的jar文件的示例:

find . -type f -printf "%f\n" -name "*.jar" | sort -f | uniq -i -d

*.jar替换为您要查找的重复文件类型。

答案 6 :(得分:1)

这是一个适合我的脚本(我不是作者)。原文和讨论可以在这里找到: http://www.daemonforums.org/showthread.php?t=4661

#! /bin/sh

# find duplicated files in directory tree
# comparing by file NAME, SIZE or MD5 checksum
# --------------------------------------------
# LICENSE(s): BSD / CDDL
# --------------------------------------------
# vermaden [AT] interia [DOT] pl
# http://strony.toya.net.pl/~vermaden/links.htm

__usage() {
  echo "usage: $( basename ${0} ) OPTION DIRECTORY"
  echo "  OPTIONS: -n   check by name (fast)"
  echo "           -s   check by size (medium)"
  echo "           -m   check by md5  (slow)"
  echo "           -N   same as '-n' but with delete instructions printed"
  echo "           -S   same as '-s' but with delete instructions printed"
  echo "           -M   same as '-m' but with delete instructions printed"
  echo "  EXAMPLE: $( basename ${0} ) -s /mnt"
  exit 1
  }

__prefix() {
  case $( id -u ) in
    (0) PREFIX="rm -rf" ;;
    (*) case $( uname ) in
          (SunOS) PREFIX="pfexec rm -rf" ;;
          (*)     PREFIX="sudo rm -rf"   ;;
        esac
        ;;
  esac
  }

__crossplatform() {
  case $( uname ) in
    (FreeBSD)
      MD5="md5 -r"
      STAT="stat -f %z"
      ;;
    (Linux)
      MD5="md5sum"
      STAT="stat -c %s"
      ;;
    (SunOS)
      echo "INFO: supported systems: FreeBSD Linux"
      echo
      echo "Porting to Solaris/OpenSolaris"
      echo "  -- provide values for MD5/STAT in '$( basename ${0} ):__crossplatform()'"
      echo "  -- use digest(1) instead for md5 sum calculation"
      echo "       $ digest -a md5 file"
      echo "  -- pfexec(1) is already used in '$( basename ${0} ):__prefix()'"
      echo
      exit 1
    (*)
      echo "INFO: supported systems: FreeBSD Linux"
      exit 1
      ;;
  esac
  }

__md5() {
  __crossplatform
  :> ${DUPLICATES_FILE}
  DATA=$( find "${1}" -type f -exec ${MD5} {} ';' | sort -n )
  echo "${DATA}" \
    | awk '{print $1}' \
    | uniq -c \
    | while read LINE
      do
        COUNT=$( echo ${LINE} | awk '{print $1}' )
        [ ${COUNT} -eq 1 ] && continue
        SUM=$( echo ${LINE} | awk '{print $2}' )
        echo "${DATA}" | grep ${SUM} >> ${DUPLICATES_FILE}
      done

  echo "${DATA}" \
    | awk '{print $1}' \
    | sort -n \
    | uniq -c \
    | while read LINE
      do
        COUNT=$( echo ${LINE} | awk '{print $1}' )
        [ ${COUNT} -eq 1 ] && continue
        SUM=$( echo ${LINE} | awk '{print $2}' )
        echo "count: ${COUNT} | md5: ${SUM}"
        grep ${SUM} ${DUPLICATES_FILE} \
          | cut -d ' ' -f 2-10000 2> /dev/null \
          | while read LINE
            do
              if [ -n "${PREFIX}" ]
              then
                echo "  ${PREFIX} \"${LINE}\""
              else
                echo "  ${LINE}"
              fi
            done
        echo
      done
  rm -rf ${DUPLICATES_FILE}
  }

__size() {
  __crossplatform
  find "${1}" -type f -exec ${STAT} {} ';' \
    | sort -n \
    | uniq -c \
    | while read LINE
      do
        COUNT=$( echo ${LINE} | awk '{print $1}' )
        [ ${COUNT} -eq 1 ] && continue
        SIZE=$( echo ${LINE} | awk '{print $2}' )
        SIZE_KB=$( echo ${SIZE} / 1024 | bc )
        echo "count: ${COUNT} | size: ${SIZE_KB}KB (${SIZE} bytes)"
        if [ -n "${PREFIX}" ]
        then
          find ${1} -type f -size ${SIZE}c -exec echo "  ${PREFIX} \"{}\"" ';'
        else
          # find ${1} -type f -size ${SIZE}c -exec echo "  {}  " ';'  -exec du -h "  {}" ';'
          find ${1} -type f -size ${SIZE}c -exec echo "  {}  " ';'
        fi
        echo
      done
  }

__file() {
  __crossplatform
  find "${1}" -type f \
    | xargs -n 1 basename 2> /dev/null \
    | tr '[A-Z]' '[a-z]' \
    | sort -n \
    | uniq -c \
    | sort -n -r \
    | while read LINE
      do
        COUNT=$( echo ${LINE} | awk '{print $1}' )
        [ ${COUNT} -eq 1 ] && break
        FILE=$( echo ${LINE} | cut -d ' ' -f 2-10000 2> /dev/null )
        echo "count: ${COUNT} | file: ${FILE}"
        FILE=$( echo ${FILE} | sed -e s/'\['/'\\\['/g -e s/'\]'/'\\\]'/g )
        if [ -n "${PREFIX}" ]
        then
          find ${1} -iname "${FILE}" -exec echo "  ${PREFIX} \"{}\"" ';'
        else
          find ${1} -iname "${FILE}" -exec echo "  {}" ';'
        fi
        echo
      done 
  }

# main()

[ ${#} -ne 2  ] && __usage
[ ! -d "${2}" ] && __usage

DUPLICATES_FILE="/tmp/$( basename ${0} )_DUPLICATES_FILE.tmp"

case ${1} in
  (-n)           __file "${2}" ;;
  (-m)           __md5  "${2}" ;;
  (-s)           __size "${2}" ;;
  (-N) __prefix; __file "${2}" ;;
  (-M) __prefix; __md5  "${2}" ;;
  (-S) __prefix; __size "${2}" ;;
  (*)  __usage ;;
esac

如果find命令不适合您,您可能需要更改它。例如

OLD :   find "${1}" -type f | xargs -n 1 basename 
NEW :   find "${1}" -type f -printf "%f\n"

答案 7 :(得分:1)

您可以使用:

find -type f  -exec readlink -m {} \; | gawk 'BEGIN{FS="/";OFS="/"}{$NF=tolower($NF);print}' | uniq -c

其中:

  • find -type f
    递归打印所有文件的完整路径。

  • -exec readlink -m {} \;
    获取文件的绝对路径

  • gawk 'BEGIN{FS="/";OFS="/"}{$NF=tolower($NF);print}'
    将所有文件名替换为小写

  • uniq -c
    唯一的路径,-c输出重复的计数。

答案 8 :(得分:0)

这一点有点晚了,但这是我的版本:

find . -type f | awk -F/ '{print $NF}' | sort -f | uniq -i -d

我们正在使用:

  1. find - 查找当前目录下的所有文件
  2. awk - 删除文件名的文件路径部分
  3. sort - 不区分大小写
  4. uniq - 通过管道查找欺骗行为
  5. (受@ mpez0回答的启发,以及@SimonDowdles对@paxdiablo回答的评论。)

答案 9 :(得分:0)

您可以使用GNU awk检查给定目录中的重复项:

gawk 'BEGINFILE {if ((seen[tolower(FILENAME)]++)) print FILENAME; nextfile}' *

在继续阅读文件之前,它使用BEGINFILE执行某些操作。在这种情况下,它会跟踪数组seen[]中出现的名称,这些名称的索引是小写文件的名称。

如果名称已经出现,无论如何,都会打印出来。否则,它只是跳转到下一个文件。

查看示例:

$ tree
.
├── bye.txt
├── hello.txt
├── helLo.txt
├── yeah.txt
└── YEAH.txt

0 directories, 5 files
$ gawk 'BEGINFILE {if ((a[tolower(FILENAME)]++)) print FILENAME; nextfile}' *
helLo.txt
YEAH.txt

答案 10 :(得分:-2)

我只是在CentOS上使用fdupes来清理整个buncha重复文件......

yum install fdupes