Тестирование

Тестирование

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

Например:

pwd
folder

Файлы в нем:

log.bkp
log
extract.bkp
extract

Мне нужно сравнить log.bkp со всеми остальными файлами, и если будет найден дубликат файла (по его содержимому), мне нужно удалить его. Аналогично, файл 'log' должен быть проверен со всеми остальными файлами, которые следуют за ним, и так далее.

Пока что я написал это, но это не дало желаемого результата.

#!/usr/bin/env ksh
count=`ls -ltrh /folder | grep '^-'|wc -l`
for i in `/folder/*`
do
   for (( j=i+1; j<=count; j++ ))
   do
      echo "Current two files are $i and $j"
      sdiff -s $i  $j
      if [ `echo $?` -eq  0 ]
      then
         echo "Contents of $i and $j are same"
       fi
    done
 done

решение1

Если вы готовы просто использовать инструмент командной строки и не хотите создавать скрипт оболочки, fdupesв большинстве дистрибутивов доступна соответствующая программа.

Существует также fslintинструмент на основе графического интерфейса, обладающий той же функциональностью.

решение2

Это решение найдет дубликаты за время O(n). Для каждого файла генерируется контрольная сумма, и каждый файл в свою очередь сравнивается с набором известных контрольных сумм через ассоциативный массив.

#!/bin/bash
#
# Usage:  ./delete-duplicates.sh  [<files...>]
#
declare -A filecksums

# No args, use files in current directory
test 0 -eq $# && set -- *

for file in "$@"
do
    # Files only (also no symlinks)
    [[ -f "$file" ]] && [[ ! -h "$file" ]] || continue

    # Generate the checksum
    cksum=$(cksum <"$file" | tr ' ' _)

    # Have we already got this one?
    if [[ -n "${filecksums[$cksum]}" ]] && [[ "${filecksums[$cksum]}" != "$file" ]]
    then
        echo "Found '$file' is a duplicate of '${filecksums[$cksum]}'" >&2
        echo rm -f "$file"
    else
        filecksums[$cksum]="$file"
    fi
done

Если вы не укажете никаких файлов (или подстановочных знаков) в командной строке, то будет использован набор файлов в текущем каталоге. Он будет сравнивать файлы в нескольких каталогах, но он не написан для рекурсии в сами каталоги.

«Первый» файл в наборе всегда считается окончательной версией. Не принимаются во внимание время файла, разрешения или владельцы. Рассматривается только содержимое.

Удалите echoиз rm -f "$file"строки, когда вы уверены, что она делает то, что вам нужно. Обратите внимание, что если вы замените эту строку на , ln -f "${filecksums[$cksum]}" "$file"вы можете сделать жесткую ссылку на содержимое. Та же экономия на диске, но вы не потеряете имена файлов.

решение3

Основная проблема в вашем скрипте, похоже, в том, что он iпринимает фактические имена файлов как значения, в то время как jэто просто число. Передача имен в массив и использование iи jв качестве индексов должно работать:

files=(*)
count=${#files[@]}
for (( i=0 ; i < count ;i++ )); do 
    for (( j=i+1 ; j < count ; j++ )); do
        if diff -q "${files[i]}" "${files[j]}"  >/dev/null ; then
            echo "${files[i]} and ${files[j]} are the same"
        fi
    done
done

(Кажется, работает с Bash и ksh/ ksh93Debian.)

Присвоение a=(this that)инициализирует массив aдвумя элементами thisи that(с индексами 0 и 1). Разделение слов и подстановка работают как обычно, поэтому files=(*)инициализируются filesименами всех файлов в текущем каталоге (кроме файлов с точками). "${files[@]}"будет расширяться до всех элементов массива, а знак решетки запрашивает длину, поэтому ${#files[@]}это количество элементов в массиве. (Обратите внимание, что это ${files}будет первый элемент массива, а ${#files}это длина первого элемента, а не массива!)

for i in `/folder/*`

Обратные кавычки здесь, наверное, опечатка? Вы бы запустили первый файл как команду, а остальные передавали бы ей в качестве аргументов.

решение4

Кстати, использование контрольной суммы или хэша — хорошая идея. Мой скрипт их не использует. Но если файлы маленькие и количество файлов не большое (например, 10-20 файлов), этот скрипт будет работать довольно быстро. Если у вас 100 файлов и больше, по 1000 строк в каждом файле, то время будет больше 10 секунд.

Использование: ./duplicate_removing.sh files/*

#!/bin/bash

for target_file in "$@"; do
    shift
    for candidate_file in "$@"; do
        compare=$(diff -q "$target_file" "$candidate_file")
        if [ -z "$compare" ]; then
            echo the "$target_file" is a copy "$candidate_file"
            echo rm -v "$candidate_file"
        fi
    done
done

Тестирование

Создать случайные файлы: ./creating_random_files.sh

#!/bin/bash

file_amount=10
files_dir="files"

mkdir -p "$files_dir"

while ((file_amount)); do
    content=$(shuf -i 1-1000)
    echo "$RANDOM" "$content" | tee "${files_dir}/${file_amount}".txt{,.copied} > /dev/null
    ((file_amount--))
done

Бегать ./duplicate_removing.sh files/* и получить вывод

the files/10.txt is a copy files/10.txt.copied
rm -v files/10.txt.copied
the files/1.txt is a copy files/1.txt.copied
rm -v files/1.txt.copied
the files/2.txt is a copy files/2.txt.copied
rm -v files/2.txt.copied
the files/3.txt is a copy files/3.txt.copied
rm -v files/3.txt.copied
the files/4.txt is a copy files/4.txt.copied
rm -v files/4.txt.copied
the files/5.txt is a copy files/5.txt.copied
rm -v files/5.txt.copied
the files/6.txt is a copy files/6.txt.copied
rm -v files/6.txt.copied
the files/7.txt is a copy files/7.txt.copied
rm -v files/7.txt.copied
the files/8.txt is a copy files/8.txt.copied
rm -v files/8.txt.copied
the files/9.txt is a copy files/9.txt.copied
rm -v files/9.txt.copied

Связанный контент