Как найти повторяющиеся имена файлов (рекурсивно) в данном каталоге? УДАР
мне нужно найти все дубликаты имен файлов в данном дереве 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)