Как найти повторяющиеся имена файлов (рекурсивно) в данном каталоге? УДАР

мне нужно найти все дубликаты имен файлов в данном дереве dir. Я не знаю, что пользователь дерева dir даст в качестве аргумента сценария, поэтому я не знаю иерархию каталогов. Я попробовал:

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

6 ответов


вот еще одно решение (основанное на предложении @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 в виде списка в переменную?


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

Да, это действительно старый вопрос. Но все эти петли и временные файлы кажутся немного громоздкими.

вот мой 1-строчный ответ:

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.


#!/bin/bash

file=`mktemp /tmp/duplicates.XXXXX` || { echo "Error creating tmp file"; exit 1; }
find  -type f |sort >  $file
awk -F/ '{print tolower($NF)}' $file |
        uniq -c|
        awk '>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

только одна команда" найти":

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

это решение записывает один временный файл во временный каталог для каждого уникального найденного файла. Во временном файле я пишу путь, где я впервые нашел уникальное имя файла, чтобы я мог вывести его позже. Итак, я создаю намного больше файлов, которые другие опубликованные решения. Но, это было то, что я мог понять.

Ниже приведен сценарий, названный 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)