디렉토리에서 중복 파일을 찾아 하나만 빼고 모두 삭제하여 공간을 확보하고 싶습니다. 쉘 스크립트를 사용하여 이를 어떻게 달성할 수 있습니까?
예를 들어:
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
동일한 기능을 가진 GUI 기반 도구 도 있습니다.
답변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
ksh
(Bash 및 / Debian 에서 작동하는 것 같습니다 ksh93
.)
이 할당은 a=(this that)
두 요소 합계(인덱스 0과 1)로 배열을 초기화합니다. 단어 분리 및 와일드카드는 평소와 같이 작동하므로 초기화는 현재 디렉터리에 있는 모든 파일 이름(점 파일 제외)으로 수행됩니다. 배열의 모든 요소로 확장되므로 해시 표기법에는 배열의 요소 수와 마찬가지로 길이가 필요합니다. (이것은 배열의 첫 번째 요소가 되며 배열이 아닌 첫 번째 요소의 길이입니다!)a
this
that
files=(*)
files
"${files[@]}"
${#files[@]}
${files}
${#files}
for i in `/folder/*`
확실히 여기 백틱은 오타인가요? 첫 번째 파일을 명령으로 실행하고 나머지 파일을 인수로 제공합니다.
답변4
그런데 체크섬이나 해시를 사용하는 것이 좋습니다. 내 스크립트는 그것을 사용하지 않습니다. 그러나 파일이 작고 파일 수가 크지 않은 경우(예: 10-20개 파일) 이 스크립트는 매우 빠르게 실행됩니다. 각각 1000줄의 파일이 100개 이상 있으면 시간은 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