파일이 이미 양쪽에있을 때 디렉토리 구조를 동기화하는 방법은 무엇입니까?


24

파일이 같은 두 개의 드라이브가 있지만 디렉토리 구조는 완전히 다릅니다.

대상 측의 모든 파일을 소스 측의 구조와 일치하도록 '이동'할 수있는 방법이 있습니까? 아마도 스크립트로?

예를 들어, A 드라이브에는 다음이 있습니다.

/foo/bar/123.txt
/foo/bar/234.txt
/foo/bar/dir/567.txt

B 드라이브에는 다음이 있습니다.

/some/other/path/123.txt
/bar/doo2/wow/234.txt
/bar/doo/567.txt

문제의 파일은 크기가 크므로 (800GB) 다시 복사하고 싶지 않습니다. 필요한 디렉토리를 만들고 파일을 이동하여 구조를 동기화하고 싶습니다.

대상에서 각 소스 파일을 찾은 다음 필요한 경우 생성하는 일치하는 디렉토리로 이동하는 재귀 스크립트를 생각하고있었습니다. 그러나-그것은 저의 능력을 넘어선 것입니다!

또 다른 우아한 솔루션이 여기에 주어졌습니다 : /superuser/237387/any-way-to-sync-directory-structure-when-the-files-are-already-on-both-sides/238086


이름이 파일의 내용을 고유하게 결정하는지 확인하십시오. 그렇지 않으면 파일을 체크섬으로 비교하는 것을 고려해야합니다.
kasterma

답변:


11

Gilles와 함께 가서 hasen j가 제안한대로 Unison을 가리킬 것 입니다. Unison은 DropBox 20 년 전 DropBox였습니다. 많은 사람들 (자체 포함)이 매일 사용하는 견고한 코드-배울 가치가 있습니다. 그래도 join얻을 수있는 모든 홍보가 필요합니다. :)


이것은 절반의 답변이지만 다시 일해야합니다 :)

기본적으로 나는 잘 알려진 작은 join유틸리티 를 보여주고 싶었습니다 . 일부 필드에서 두 테이블을 조인합니다.

먼저 파일 이름에 공백이 포함 된 테스트 사례를 설정하십시오.

for d in a b 'c c'; do mkdir -p "old/$d"; echo $RANDOM > "old/${d}/${d}.txt"; done
cp -r old new

(에서 일부 디렉토리 및 / 또는 파일 이름을 편집하십시오 new).

이제 각 디렉토리에 대해 해시-> 파일 이름 맵을 작성한 다음 join동일한 해시가있는 파일을 일치시키는 데 사용 하려고합니다. 지도를 생성하려면 다음을 입력하십시오 makemap.sh.

find "$1" -type f -exec md5 -r "{}" \; \
  | sed "s/\([a-z0-9]*\) ${1}\/\(.*\)/\1 \"\2\"/" \

makemap.sh 'hash "filename' '형식의 줄로 파일을 뱉어 내기 때문에 첫 번째 열에서 조인합니다.

join <(./makemap.sh 'old') <(./makemap.sh 'new') >moves.txt

이것은 moves.txt다음과 같은 것을 생성 합니다.

49787681dd7fcc685372784915855431 "a/a.txt" "bar/a.txt"
bfdaa3e91029d31610739d552ede0c26 "c c/c c.txt" "c c/c c.txt"

다음 단계는 실제로 이동을 할 것이다, 그러나 나의 시도는 인용에 붙어있어 ... mv -i그리고 mkdir -p편리 와야한다.


죄송합니다,이 중 어느 것도 이해하지 못합니다!
Dan

1
join정말 흥미 롭습니다. 관심을 가져 주셔서 감사합니다.
Steven D

@단. 죄송합니다. 문제는 파일 이름에 대해 어떤 가정을 할 수 있는지 모른다는 것입니다. 가정이없는 스크립팅은 재미가 없습니다. 특히 파일 이름을 dwheeler.com/essays/fixing-unix-linux-filenames.html 파일로 출력하기로 선택한 경우에는 재미가 없습니다 .
Janus

1
MD5 해시를 작성하기 위해 이러한 큰 파일을 완전히 읽어야하므로 시간과 CPU로드가 많이 소요될 수 있습니다. 파일 이름과 파일 크기가 일치하면 파일을 해시하는 것이 과도합니다. 해싱은 두 번째 단계에서 이름이나 크기가 하나 이상 (동일한 디스크에서) 일치하는 파일에 대해서만 수행해야합니다.
Hauke ​​Laging 2013

join입력으로 사용하는 파일을 정렬 할 필요가 없습니까?
cjm

8

unison이라는 유틸리티가 있습니다.

http://www.cis.upenn.edu/~bcpierce/unison/

사이트 설명 :

Unison은 Unix 및 Windows 용 파일 동기화 도구입니다. 파일 및 디렉토리 모음의 두 복제본을 다른 호스트 (또는 동일한 호스트의 다른 디스크)에 저장하고 개별적으로 수정 한 다음 각 복제본의 변경 사항을 다른 복제본으로 전파하여 최신 상태로 유지할 수 있습니다.

루트 중 하나 이상이 원격 인 경우 Unison은 첫 번째 실행에서 이동 된 파일 만 감지하므로 로컬 파일을 동기화하는 경우에도 ssh://localhost/path/to/dir루트 중 하나로 사용 하십시오.


@Gilles : 확실합니까? 나는 모든 것을 일제히 사용하고 종종 이름이 바뀌거나 멀리 떨어진 파일을 발견합니다. 이것은 단일 노드가 inode 번호를 기록 할 수있는 이미 동기화 된 파일 (또는 다른 트릭 사용)에 대해서만 작동한다고 말하고 있습니까?
Janus

@Janus : 수정 해 주셔서 감사합니다. 제 의견은 실제로 틀 렸습니다. Unison은 초기 실행시에도 이동 된 파일을 감지합니다. (두 루트가 모두 로컬 인 경우에는이 작업을 수행하지 않으므로 테스트에서이 작업을 수행하지 않은 것입니다.) 따라서 단일 제안은 매우 좋은 제안입니다.
Gilles 'SO- 악마 그만'

@ 질. 알아두면 좋은 점-알고리즘이 로컬 동기화와 원격 동기화를 구분하는 곳이 꽤있는 것 같습니다. 실제로 첫 번째 동기화에서는 작동하지 않는다고 생각했습니다. 한 번에 +1!
Janus

4

hasen j에서 제안한 대로 Unison사용하십시오 . 이 답변을 잠재적으로 유용한 스크립팅 예제로 사용하거나 기본 유틸리티 만 설치된 서버에서 사용하려고합니다.


파일 이름이 계층 전체에서 고유하다고 가정하겠습니다. 또한 파일 이름에 줄 바꿈이 포함되어 있지 않으며 디렉토리 트리에는 디렉토리와 일반 파일 만 포함되어 있다고 가정합니다.

  1. 먼저 소스 측에서 파일 이름을 수집하십시오.

    (cd /A && find . \! -type d) >A.find
  2. 그런 다음 파일을 대상 측으로 이동하십시오. 먼저 대상 측에 평평한 파일 트리를 만듭니다. 이전 계층 구조에서 하드 링크를 유지 하려면 ln대신 사용하십시오 mv.

    mkdir /B.staging /B.new
    find /B.old -type f -exec sh -c 'mv -- "$@" "$0"' /B.staging {} +
  3. 대상에서 일부 파일이 누락 된 경우 유사하게 병합 된 파일을 작성하고 /A.stagingrsync를 사용하여 소스에서 대상으로 데이터를 복사하십시오.

    rsync -au /A.staging/ /B.staging/
  4. 이제 파일 이름을 변경하십시오.

    cd /B.new &&
    <A.find perl -l -ne '
      my $dir = '.'; s!^\./+!!;
      while (s!^([^/]+)/+!!) {  # Create directories as needed
        $dir .= "/$1";
        -d $dir or mkdir $dir or die "mkdir $dir: $!"
      }
      rename "/B.staging/$_", "$dir/$_" or die "rename -> $dir/$_: $!"
    '

    동등하게 :

    cd /B.new &&
    <A.find python -c '
    import os, sys
    for path in sys.stdin.read().splitlines():
        dir, base = path.rsplit("/", 2)
        os.rename(os.path.join("/B.new", base), path)
    '
  5. 마지막으로, 디렉토리의 메타 데이터에 관심이있는 경우 이미 존재하는 파일로 rsync를 호출하십시오.

    rsync -au /A/ /B.new/

이 게시물에서 스 니펫을 테스트하지 않았습니다. 자신의 책임하에 사용하십시오. 의견에 오류를보고하십시오.


2

특히 진행중인 동기화가 유용한 경우 git-annex 를 알아낼 수 있습니다.

비교적 새롭습니다. 나는 그것을 직접 사용하려고하지 않았습니다.

파일의 두 번째 사본을 유지하지 않기 때문에 제안 할 수 있습니다. 이는 특정 비 Git 버전 제어 시스템과 같이 파일을 읽기 전용 ( "잠금")으로 표시해야 함을 의미합니다.

파일은 sha256sum + 파일 확장자 (기본적으로)로 식별됩니다. 따라서 쓰기를 수행하지 않고 (필요한 경우 저 대역폭 네트워크를 통해) 동일한 파일 내용이지만 다른 파일 이름으로 두 개의 저장소를 동기화 할 수 있어야합니다. 물론 파일을 체크섬하기 위해서는 모든 파일을 읽어야합니다.


1

이런 식으로 어떻습니까 :

src=/mnt/driveA
dst=/mnt/driveB

cd $src
find . -name <PATTERN> -type f >/tmp/srclist
cd $dst
find . -name <PATTERN> -type f >/tmp/dstlist

cat /tmp/srclist | while read srcpath; do
    name=`basename "$srcpath"`
    srcdir=`dirname "$srcpath"`
    dstpath=`grep "/${name}\$" /tmp/dstlist`

    mkdir -p "$srcdir"
    cd "$srcdir" && ln -s "$dstpath" "$name"
done

이것은 동기화하려는 파일 이름이 전체 드라이브에서 고유하다고 가정합니다. 그렇지 않으면 완전히 자동화 할 수있는 방법이 없습니다 (단, 파일이 하나 이상있는 경우 선택할 파일을 선택하라는 메시지를 사용자에게 제공 할 수 있음).

위의 스크립트는 간단한 경우에 작동하지만 name정규 표현식에 특별한 의미가있는 기호가 포함되어 있으면 실패 할 수 있습니다. grep파일의 많은이 있다면 파일의 목록은 시간이 많이 걸릴 수 있습니다. 파일 이름을 경로 (예 : Ruby)에 매핑하는 해시 테이블을 사용하도록이 코드를 변환하는 것을 고려할 수 있습니다.


이것은 유망 해 보이지만 파일을 이동 시키거나 심볼릭 링크를 생성합니까?
Dan

나는 이것을 대부분 이해한다고 생각한다. 그러나 grep선은 무엇을 하는가? 그냥 일치하는 파일의 전체 경로를 찾 dstlist습니까?
Dan

@ Dan : 분명히 ln그것을 사용하여 심볼릭 링크를 만듭니다. mv파일을 이동 하는 데 사용할 수 있지만 기존 파일을 덮어 쓰지 않도록주의하십시오. 또한 파일을 이동 한 후 빈 디렉토리를 정리할 수 있습니다. 예,이 grep명령은 파일 이름으로 끝나는 행을 검색하여 대상 드라이브에서 해당 파일의 전체 경로를 나타냅니다.
alex

1

기본 파일 이름이 트리에서 고유하다고 가정하면 매우 간단합니다.

join <(cd A; find . -type f | while read f; do echo $(basename $f) $(dirname $f); done | sort) \
     <(cd B; find . -type f | while read f; do echo $(basename $f) $(dirname $f); done | sort) |\
while read name to from
do
        mkdir -p B/$to
        mv -v B/$from/$name B/$to/
done

오래된 빈 디렉토리를 정리하려면 다음을 사용하십시오.

find B -depth -type d -delete

1

나는 또한이 문제에 직면했다. 파일을 webdav마운트에 동기화하기 때문에 md5sum 기반 솔루션이 작동하지 않았습니다 . webdav대상 에서 md5sum 합계를 계산하면 큰 파일 작업이 필요합니다.

가장 많이 움직이는 파일 reorg_Remote_Dir_detect_moves.sh 을 감지하려고 하는 작은 스크립트 (github에서) 를 만든 다음 원격 디렉토리를 조정하는 몇 가지 명령으로 새로운 임시 쉘 스크립트를 만듭니다. 파일 이름 만 처리하므로 스크립트는 완벽한 솔루션이 아닙니다.

안전을 위해 다음과 같은 여러 파일이 무시됩니다. A) 모든면에 동일한 (시작이 같은) 이름을 가진 파일 및 B) 원격면에만있는 파일. 무시되고 건너 뜁니다.

그런 다음 건너 뛴 파일은 선호하는 동기화 도구 (예 : rsync, unison...)에 의해 처리되며 임시 쉘 스크립트를 실행 한 후에 사용해야합니다.

어쩌면 내 스크립트가 누군가에게 유용할까요? 그렇다면 (더 명확하게하기 위해) 세 단계가 있습니다.

  1. 쉘 스크립트를 실행하십시오 reorg_Remote_Dir_detect_moves.sh (github에서)
  2. 이것은 임시 쉘 스크립트를 생성합니다 /dev/shm/REORGRemoteMoveScript.sh=> 이동을 위해 이것을 실행합니다 (마운트가 빠릅니다 webdav)
  3. 선호하는 동기화 도구를 실행하십시오 (예 : rsync, unison...)

1

여기에 대한 답변이 있습니다. 미리, 모든 스크립팅 경험은 bash에서 온 것이므로 다른 쉘을 사용하는 경우 명령 이름이나 구문이 다를 수 있습니다.

이 솔루션에는 두 개의 별도 스크립트를 만들어야합니다.

이 첫 번째 스크립트는 실제로 대상 드라이브에서 파일을 이동합니다.

md5_map_file="<absolute-path-to-a-temporary-file>"

# Given a single line from the md5 map file, list
# only the path from that line.
get_file()
{
  echo $2
}

# Given an md5, list the filename from the md5 map file
get_file_from_md5()
{
  # Grab the line from the md5 map file that has the
  # md5 sum passed in and call get_file() with that line.
  get_file `cat $md5_map_file | grep $1`
}

file=$1

# Compute the md5
sum=`md5sum $file`

# Get the new path for the file
new_file=`get_file_from_md5 $sum`

# Make sure the destination directory exists
mkdir -p `dirname $new_file`
# Move the file, prompting if the move would cause an overwrite
mv -i $file $new_file

두 번째 스크립트는 첫 번째 스크립트에서 사용하는 md5 맵 파일을 만든 다음 대상 드라이브의 모든 파일에서 첫 번째 스크립트를 호출합니다.

# Do not put trailing /
src="<absolute-path-to-source-drive>"
dst="<absolute-path-to-destination-drive>"
script_path="<absolute-path-to-the-first-script>"
md5_map_file="<same-absolute-path-from-first-script>"


# This command searches through the source drive
# looking for files.  For every file it finds,
# it computes the md5sum and writes the md5 sum and
# the path to the found filename to the filename stored
# in $md5_map_file.
# The end result is a file listing the md5 of every file
# on the source drive
cd $src
find . -type f -exec md5sum "{}" \; > $md5_map_file

# This command searches the destination drive for files and calls the first
# script for every file it finds.
cd $dst
find . -type f -exec $script_path '{}' \; 

기본적으로 두 스크립트는와 연관 배열을 단순화합니다 $md5_map_file. 먼저 소스 드라이브의 파일에 대한 모든 md5가 계산되어 저장됩니다. md5와 연관된 것은 드라이브 루트의 상대 경로입니다. 그런 다음 대상 드라이브의 각 파일에 대해 md5가 계산됩니다. 이 md5를 사용하여 소스 드라이브에서 해당 파일의 경로를 찾습니다. 그런 다음 대상 드라이브의 파일이 원본 드라이브의 파일 경로와 일치하도록 이동됩니다.

이 스크립트에는 몇 가지주의 사항이 있습니다.

  • $ dst의 모든 파일도 $ src에 있다고 가정합니다.
  • $ dst에서 디렉토리를 제거하지 않고 파일 만 이동합니다. 나는 현재 이것을 자동으로 수행하는 안전한 방법을 생각할 수 없다

md5를 계산하는 데 시간이 오래 걸립니다. 모든 내용을 실제로 읽어야합니다. Dan이 파일이 동일하다고 확신하는 경우 디렉토리 구조에서 파일을 이동하는 것은 매우 빠릅니다 (읽지 않음). 그래서 md5sum여기서 사용하지 않는 것 같습니다. (BTW, rsync체크섬을 계산하지 않는 모드가 있습니다.)
imz-Ivan Zakharyaschev

정확성과 속도의 균형입니다. 단순한 파일 이름보다 높은 정확도를 사용하는 방법을 제공하고 싶었습니다.
cledoux
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.