Как найти дубликаты файлов с одинаковым именем, но в другом случае, которые существуют в одном каталоге в Linux? - PullRequest
29 голосов
/ 21 января 2010

Как я могу вернуть список файлов с именами дубликатов, то есть с одинаковыми именами, но в другом случае, которые существуют в том же каталоге ?

Мне плевать на содержимое файлов. Мне просто нужно знать местоположение и имя любых файлов, которые имеют дубликаты с одинаковыми именами.

Пример дубликатов:

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

В идеале мне нужно рекурсивно искать все файлы из базовой директории. В приведенном выше примере это было /www/

Ответы [ 11 ]

38 голосов
/ 21 января 2010

Другой ответ отличный, но вместо «довольно чудовищного» сценария 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";
    }
}
35 голосов
/ 21 января 2010

Попробуйте:

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 " убирает те строки, где счет был один.

Когда я запускаю это в каталоге с одним «дубликатом» (я скопировал qq в qQ), я получаю:

2 qq

Для версии «этот каталог и каждый подкаталог» просто замените ls -1 на find . или find DIRNAME, если вы хотите указать определенную начальную точку каталога (DIRNAME - это имя каталога, которое вы хотите использовать).

Это возвращает (для меня):

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, чтобы ограничить возвращаемое.

5 голосов
/ 21 января 2010

Я верю

ls | sort -f | uniq -i -d

проще, быстрее и даст тот же результат

2 голосов
/ 13 марта 2013

Вот пример, как найти все дубликаты jar-файлов:

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

Замените *.jar тем типом дубликатов файлов, который вы ищете.

2 голосов
/ 31 августа 2012

Это симпатичное небольшое приложение командной строки, которое называется findsn, которое вы получите, если скомпилируете fslint, который не входит в пакет deb.

он найдет любые файлы с одинаковым именем и молниеносно, и он может обрабатывать разные случаи.

/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 является более строгой, так как только файлы (или каталоги) в том же каталоге, чьи имена отличаются только регистром. И.Е. -c будет отмечать файлы и каталоги, которые будут конфликтовать при передаче к нечувствительной к регистру файловой системе. Обратите внимание, если указаны -c или -C и пути не указаны, предполагается, что текущий каталог.

2 голосов
/ 15 июля 2011

Следуя ответу mpez0, для рекурсивного обнаружения просто замените «ls» на «find». Единственная проблема, которую я вижу с этим, состоит в том, что если это дублирующий каталог, то у вас есть 1 запись для каждого файла в этом каталоге. Некоторый человеческий мозг должен лечить результат этого.

Но в любом случае, вы автоматически не удаляете эти файлы?

find . | sort -f | uniq -i -d
1 голос
/ 19 декабря 2013

Вы можете использовать:

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 выводит количество дубликатов.

1 голос
/ 25 августа 2013

Вот сценарий, который работал для меня (я не автор). оригинал и обсуждение можно найти здесь: 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"
0 голосов
/ 22 августа 2016

Вы можете проверить дубликаты в заданном каталоге с помощью 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
0 голосов
/ 27 января 2015

Немного опоздал к этому, но вот версия, с которой я пошел:

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

Здесь мы используем:

  1. find - найти все файлы в текущем каталоге
  2. awk - удалить часть пути к файлу имени файла
  3. sort - сортировка без учета регистра
  4. uniq - найдите обманщиков из того, что делает через трубу

(Вдохновлено ответом @ mpez0 и @SimonDowdles комментирует ответ @paxdiablo.)

...