bash에서 디렉토리의 모든 파일을 비교하는 방법은 무엇입니까?

bash에서 디렉토리의 모든 파일을 비교하는 방법은 무엇입니까?

주어진 예:

dir1/
 file1
 file2
 file3
 file4
  • file1-file2, file1-file3, file1-file4그럼 어떻게 비교하나요 file2->file3, file2->file4? 이 예에서 이름은 file_number이지만 다음과 같을 수도 있습니다.어떤 이름.
  • 기본적으로 하나의 diff파일을 다른 파일과 비교하는 것이 아니라 모든 파일을 모든 파일과 비교하십시오.

나는 노력해 왔습니다:

for f in $(find ./ -type f | sort | uniq); do

    compare=$(diff -rq "$f" "$1" &>/dev/null)
    if [[ $compare -eq $? ]]; then
        echo "$(basename "$f") yes equal $1"
    else
        echo "$(basename "$f") no equal $1"
    fi
done

반품

./file1 yes equal ./file1
./file2 no equal ./file1
./file3 yes equal ./file1
./script no equal ./file1
./sd no equal ./file1
  • 임의의 파일 번호만 file1과 비교
  • 또 다른 루프가 필요한 것 같지만 이제 [버블 정렬 알고리즘과 같은] 인벤토리가 있습니다.
  • file1과 같음 file1 [동일 파일] 비교를 중지하기 위해 다른 IF 문을 만드는 방법

답변1

예, 두 개의 루프가 필요합니다. 그러나 diff 출력을 사용할 수 있는 diff/dev/null로 삭제하므로 그럴 필요가 없는 것 같습니다 .cmp

예를 들어:

#!/bin/bash

# Read the list of files into an array called $files - we're iterating over
# the same list of files twice, but there's no need to run find twice.
#
# This uses NUL as the separator between filenames, so will work with
# filenames containing ANY valid character. Requires GNU find and GNU
# sort or non-GNU versions that support the `-print0` and `-z` options.
#
# The `-maxdepth 1` predicate prevents recursion into subdirs, remove it
# if that's not what you want.
mapfile -d '' -t files < <(find ./ -maxdepth 1 -type f -print0 | sort -z -u)

for a in "${files[@]}" ; do
  for b in "${files[@]}" ; do
    if [ ! "$a" = "$b" ] ; then
      if cmp --quiet "$a" "$b" ; then
        echo "Yes. $a is equal to $b"
      else
        echo "No. $a is not equal to $b"
      fi
    fi
  done
done

그런데, 이렇게 하면 많은 출력이 생성됩니다(n × (n-1)출력 라인, 여기서 n은 파일 수). 개인적 으로 다른 파일과 동일한 파일은 고유한 파일보다 훨씬 덜 일반적일 가능성이 높기 때문에 else및 행을 제거하거나 주석 처리하겠습니다 .echo "No...."


또한 파일 abc과 파일 xyz이 동일하면 파일을 두 번 비교하고 Yes를 두 번 인쇄합니다.

Yes. ./abc is equal to ./xyz
Yes. ./xyz is equal to ./abc

이를 방지하는 방법에는 여러 가지가 있습니다. 가장 간단한 방법은 연관 배열을 사용하여 서로 비교하는 파일을 추적하는 것입니다. 예를 들어

#!/bin/bash

# Read the list of files into an array called $files
mapfile -d '' -t files < <(find ./ -maxdepth 1 -type f -print0 | sort -z -u)

# declare that $seen is an associative array
declare -A seen

for a in "${files[@]}" ; do
  for b in "${files[@]}" ; do
    if [ ! "$a" = "$b" ] && [ -z "${seen[$a$b]}" ] && [ -z "${seen[$b$a]}" ] ; then
      seen[$a$b]=1
      seen[$b$a]=1
      if cmp --quiet "$a" "$b" ; then
        echo "Yes. $a is equal to $b"
      #else
      #  echo "No. $a is not equal to $b"
      fi
    fi
  done
done

답변2

많은 파일을 쌍으로 비교하는 것은 빠르게 번거로울 수 있습니다. 10개 파일에는 45번의 비교가 필요합니다. 100개의 파일에는 거의 5000개의 파일이 필요합니다. 내 테스트 세트는 595개 파일(총 10GB)이며 175,000회 이상의 쌍별 비교가 필요합니다. (이 세트는 다양한 파티션의 전체 및 부분 백업에서 얻은 메타데이터를 포함하는 9개의 오래된 아카이브 디렉터리 세트입니다.)

접근 방식은 각 파일의 체크섬을 계산한 다음(노트북에서 총 2분 조금 넘게 소요) awk를 사용하여 체크섬별로 파일을 그룹화하는 것입니다(1초 미만 소요).

체크섬 프로세스는 다음 Bash 조각입니다.

#.. Checksum all the files, and show some statistics.
[ x ] && {
    time ( cd "${BackUpDir}" && cksum */* ) > "${CkSums}"
    du -s -h "${BackUpDir}"
    head -n 3 "${CkSums}"
    awk '{ Bytes += $2; }
        END { printf ("Files %d, %.2f MB\n", FNR, Bytes / (1024 * 1024)); }
        ' "${CkSums}"
}

이 로그를 보여주세요.

$ ./fileGroup
real    2m5.139s
user    1m3.141s
sys 0m24.685s
9.8G    /media/paul/WinData/tarMETADATA
2288228966 156844 20220107_002000/02_History.tld
1812380507 156992 20220107_002000/02_History.toc
3028427874 1000411 20220107_002000/06_TechHist.tld
Files 565, 10001.10 MB

real    0m0.024s  #.. (Runtime of the awk component)
user    0m0.018s
sys 0m0.008s

결과 발췌:

Group of 5 files for cksum 1459775330
    20220319_114500/lib64.tld
    20220401_182500/lib64.tld
    20220407_192000/lib64.tld
    20220503_190500/lib64.tld
    20220503_232500/lib64.tld

Group of 3 files for cksum 2937156162
    20220407_192000/sbin.tld
    20220503_190500/sbin.tld
    20220503_232500/sbin.tld

Group of 2 files for cksum 3291901599
    20220503_190500/30_Photos.tld
    20220503_232500/30_Photos.tld

Counted 304 non-grouped files.

Bash 스크립트는 약 60줄로 구성되어 있으며 그 중 30줄은 awk 스크립트가 포함되어 있습니다(필요한 GNU/특정 구문은 모르겠습니다).

#! /bin/bash --

#.. Determine groups of identical files.

BackUpDir="/media/paul/WinData/tarMETADATA"
CkSums="Cksum.txt"
Groups="Groups.txt"

#.. Group all the files by checksum, and report them.

fileGroup () {

    local Awk='
BEGIN { Db = 0; reCut2 = "^[ ]*[^ ]+[ ]+[^ ]+[ ]+"; }
{ if (Db) printf ("\n%s\n", $0); }

#.. Add a new cksum value.
! (($1,0) in Fname) {
    Cksum[++Cksum[0]] = $1;
    if (Db) printf ("Added Cksum %d value %s.\n", 
        Cksum[0], Cksum[Cksum[0]]);
    Fname[$1,0] = 0;
}
#.. Add a filename.
{
    Fname[$1,++Fname[$1,0]] = $0;
    sub (reCut2, "", Fname[$1,Fname[$1,0]]);
    if (Db) printf ("Fname [%s,%s] is \047%s\047\n",
        $1, Fname[$1,0], Fname[$1, Fname[$1,0]]);
}
#.. Report the identical files, grouped by checksum.
function Report (Local, k, ke, cs, j, je, Single) {
    ke = Cksum[0];
    for (k = 1; k <= ke; ++k) {
        cs = Cksum[k];
        je = Fname[cs,0];
        if (je < 2) { ++Single; continue; }
        printf ("\nGroup of %d files for cksum %s\n", je, cs);
        for (j = 1; j <= je; ++j) printf ("    %s\n", Fname[cs,j]);
    }
    printf ("\nCounted %d non-grouped files.\n", Single);
}
END { Report( ); }
'
    awk -f <( printf '%s' "${Awk}" )
}

#### Script Body Starts Here.

    #.. Checksum all the files, and show some statistics.
    [ x ] && {
        time ( cd "${BackUpDir}" && cksum */* ) > "${CkSums}"
        du -s -h "${BackUpDir}"
        head -n 3 "${CkSums}"
        awk '{ Bytes += $2; }
            END { printf ("Files %d, %.2f MB\n", FNR, Bytes / (1024 * 1024)); }
            ' "${CkSums}"
    }

    #.. Analyse the cksum data.
    time fileGroup < "${CkSums}" > "${Groups}"

관련 정보