it-roy-ru.com

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

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

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

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

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

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

28
Camsoft

Другой ответ великолепен, но вместо «довольно чудовищного» сценария 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";
    }
}
37
Christoffer Hammarström

Пытаться:

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

33
paxdiablo

Я верю

ls | sort -f | uniq -i -d

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

5
mpez0

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

2
user1639307

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

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

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

2
noclayto

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

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

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

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

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

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

Куда:

  • find -type f
    recursion распечатать полный путь к файлу. 

  • -exec readlink -m {} \;
    получить абсолютный путь к файлу

  • gawk 'BEGIN{FS="/";OFS="/"}{$NF=tolower($NF);print}'
    заменить все имена файлов на нижний регистр

  • uniq -c
    уникальный путь, -c выводит количество дубликатов.

1
user3119102

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

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

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

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

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

0
serg10

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