
디렉토리를 읽고 내용이 중복될 수 있는 파일을 식별하려고 합니다.
단순화된 버전:
#!/bin/bash
IFS=$'\n'
for FILEA in $(find "$1" -type f -print)
do
echo "$FILEA"
for FILEB in $(find "$1" -type f -print)
do
echo cmp \"${FILEA}\" \"${FILEB}\"
cmp \"${FILEA}\" \"${FILEB}\"
done
done
그러나 모든 cmp
인스턴스가 불평합니다 No such file or directory
.
cmp 명령의 에코된 버전을 얻으면 예상한 대로 정확히 작동합니다.
cmp가 스크립트 컨텍스트에서 실행될 때 파일을 볼 수 없는 이유는 무엇입니까?
답변1
주요 문제는 사용할 때 파일 이름에 리터럴 큰따옴표를 추가하는 것입니다 cmp
(그리고 파일 이름 자체는 실제로 따옴표가 없습니다). 이것이 파일을 찾을 수 없는 이유입니다(이름에 따옴표가 없습니다). 출력을 반복할 수도 있습니다 find
.이건 이상적이지 않아.
만약 너라면진짜사용하고 싶지 않음fdupes
, 다음을 수행하여 방법을 보다 효율적으로(속도 측면에서) 만들 수 있습니다.
#!/bin/bash
# enable the "**" globbing pattern,
# remove non-matched patterns rather than keeping them unexpanded, and
# allow the matching of hidden names:
shopt -s globstar nullglob dotglob
pathnames=("${1:-.}"/**) # all pathnames beneath "$1" (or beneath "." if "$1" is empty)
# loop over the indexes of the list of pathnames
for i in "${!pathnames[@]}"; do
this=${pathnames[i]} # the current pathname
# skip this if it's not a regular file (or a symbolic link to one)
[[ ! -f "$this" ]] && continue
# loop over the remainder of the list
for that in "${pathnames[@]:i+1}"; do
# skip that if it's not a regular file (or a symbolic link to one)
[[ ! -f "$that" ]] && continue
# compare and print if equal
if [[ "$this" -ef "$that" ]] || cmp -s "$this" "$that"; then
printf '"%s" and "%s" contains the same thing\n' "$this" "$that"
fi
done
done
이렇게 하면 각 파일에 대해 전체 디렉터리 구조를 한 번씩 살펴보는 일이 방지되고(이 작업은 내부 루프에서 수행됨) 쌍을 여러 번 비교하는 일도 방지됩니다. 아직이야매우cmp
전체 디렉터리 계층 구조의 모든 파일 조합에서 실행 해야 하기 때문에 속도가 느립니다 .
대신 더 간단한 접근 방식을 시도해 볼 수도 있습니다.
#!/bin/bash
tmpfile=$(mktemp)
find "${1:-.}" -type f -exec md5sum {} + | sort -o "$tmpfile"
awk 'FNR == NR && seen[$1]++ { next } seen[$1] > 1' "$tmpfile" "$tmpfile"
rm -f "$tmpfile"
모든 파일의 MD5 체크섬을 계산하고 목록을 정렬한 후 임시 파일에 저장합니다. 그런 다음 출력은 모든 중복 파일을 awk
추출하는 데 사용됩니다 .md5sum
출력은 다음과 같습니다.
$ bash ~/script.sh
01b1688f97f94776baae85d77b06048b ./QA/StackExchange/.git/hooks/pre-commit.sample
01b1688f97f94776baae85d77b06048b ./Repositories/password-store.git/hooks/pre-commit.sample
036208b4a1ab4a235d75c181e685e5a3 ./QA/StackExchange/.git/info/exclude
036208b4a1ab4a235d75c181e685e5a3 ./Repositories/password-store.git/info/exclude
054f9ffb8bfe04a599751cc757226dda ./QA/StackExchange/.git/hooks/pre-applypatch.sample
054f9ffb8bfe04a599751cc757226dda ./Repositories/password-store.git/hooks/pre-applypatch.sample
2b7ea5cee3c49ff53d41e00785eb974c ./QA/StackExchange/.git/hooks/post-update.sample
2b7ea5cee3c49ff53d41e00785eb974c ./Repositories/password-store.git/hooks/post-update.sample
3c5989301dd4b949dfa1f43738a22819 ./QA/StackExchange/.git/hooks/pre-push.sample
3c5989301dd4b949dfa1f43738a22819 ./Repositories/password-store.git/hooks/pre-push.sample
위 출력에는 중복된 내용이 일부 있습니다.오른쪽파일 수.
파일 이름에 개행 문자가 포함된 경우 접두사 md5sum
에 문자가 붙은 행이 출력됩니다 \
.
$ touch $'hello\nworld'
$ md5sum *
\d41d8cd98f00b204e9800998ecf8427e hello\nworld
이를 올바르게 처리하려면(줄 시작 부분에서 백슬래시를 제거하여) 스크립트의 첫 번째 파이프를 다음과 같이 수정해야 할 수도 있습니다.
find "${1:-.}" -type f -exec md5sum {} + | sed 's/^\\//' | sort -o "$tmpfile"