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


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.