ファイルがすでに両側にあるときにディレクトリ構造を同期する方法はありますか?


24

同じファイルを持つ2つのドライブがありますが、ディレクトリ構造はまったく異なります。

ソース側の構造と一致するように、宛先側のすべてのファイルを「移動」する方法はありますか?おそらくスクリプトで?

たとえば、ドライブ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)なので、それらを再コピーしたくありません。必要なディレクトリを作成し、ファイルを移動して、構造を同期したいだけです。

宛先で各ソースファイルを見つけ、一致するディレクトリに移動し、必要に応じて作成する再帰的なスクリプトを考えていました。しかし、それは私の能力を超えています!

別のエレガントなソリューションがここに与えられました:https : //superuser.com/questions/237387/any-way-to-sync-directory-structure-when-the-files-are-already-on-both-sides/238086


名前がファイルの内容を一意に決定していることを確認してください。そうでない場合は、チェックサムでファイルを比較することを検討してください。
カステルマ

回答:


11

ジルと一緒に行き、hasen jが提案するUnisonを紹介します。UnisonはDropBoxの20年前のDropBoxでした。多くの人々(私自身も含む)が毎日使用する堅実なコード-学ぶのに非常に価値があります。それでも、joinそれが得ることができるすべての宣伝が必要です:)


これは半分の答えですが、仕事に戻る必要があります:)

基本的に、私はそれを行うあまり知られjoinていないユーティリティをデモンストレーションしたかったのです。あるフィールドで2つのテーブルを結合します。

最初に、スペースを含むファイル名を含むテストケースを設定します。

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便利になるはずです。


申し訳ありませんが、これは理解できません。
ダン

1
join本当に面白いです。私の注意を引いてくれてありがとう。
スティーブンD

@ダン。ごめんなさい。問題は、あなたのファイル名についてどのような仮定ができるかわからないことです。仮定なしのスクリプティングは面白くありません。特にこの場合、ファイル名をファイルdwheeler.com/essays/fixing-unix-linux-filenames.htmlに出力することを選択した場合です。
ヤヌス

1
MD5ハッシュを作成するにはこれらの巨大なファイルを完全に読み取る必要があるため、これはおそらく多くの時間(およびCPU負荷)を無駄にします。ファイル名とファイルサイズが一致する場合、おそらくファイルをハッシュするのはやり過ぎです。ハッシュは2番目のステップで、名前またはサイズが(同じディスク上の)少なくとも1つに一致するファイルに対してのみ行う必要があります。
ハウケレイジング

join入力として使用するファイルを並べ替える必要はありませんか?
cjm

8

unisonというユーティリティがあります:

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

サイトからの説明:

Unisonは、UnixおよびWindows用のファイル同期ツールです。ファイルとディレクトリのコレクションの2つのレプリカを異なるホスト(または同じホスト上の異なるディスク)に保存し、別々に変更してから、各レプリカの変更を他のレプリカに伝播して最新の状態にすることができます。

少なくとも1つのルートがリモートである場合、Unisonは最初の実行でのみ移動ファイルを検出するため、ローカルファイルを同期している場合でもssh://localhost/path/to/dir、ルートの1つとして使用することに注意してください。


@Gilles:よろしいですか?私はすべてにユニゾンを使用していますが、名前が変更されたり、遠くに移動されたファイルを見つけることがよくあります。これは、ユニゾンがiノード番号(または他のトリック)を記録する機会があった、既に同期されたファイルに対してのみ機能すると言っていますか?
ヤヌス

@ヤヌス:訂正してくれてありがとう、私のコメントは本当に間違っていた。Unisonは、最初の実行時でも、移動されたファイルを検出します。(両方のルートがローカルの場合はこれを行いません。これが、私のテストではそうしなかった理由です。)したがって、ユニゾンは非常に良い提案です。
ジル 'SO-悪であるのをやめる'

@Gilles。知っておきたいこと-アルゴリズムがローカル同期とリモート同期を区別する場所はかなりあるようです。実際、最初の同期ではうまくいかないと思いました。ユニゾンで+1!
ヤヌス

4

hasen jが提案するUnison使用します。この回答は、潜在的に有用なスクリプトの例として、または基本的なユーティリティのみがインストールされているサーバーで使用するために残しています。


ファイル名は階層全体で一意であると仮定します。また、改行を含むファイル名はなく、ディレクトリツリーにはディレクトリと通常のファイルのみが含まれていると仮定します。

  1. 最初にソース側でファイル名を収集します。

    (cd /A && find . \! -type d) >A.find
  2. 次に、ファイルを宛先側の所定の場所に移動します。最初に、宛先側でファイルのフラットツリーを作成します。古い階層にハードリンクを保持する場合lnmv、代わりに使用します。

    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を見つけ出すことができます。

それは比較的新しいです。私はそれを自分で使用しようとしませんでした。

ファイルの2番目のコピーを保持することを避けるため、提案できます。これは、特定の非Gitバージョン管理システムのように、ファイルを読み取り専用(「ロック」)としてマークする必要があることを意味します。

ファイルは、sha256sum +ファイル拡張子(デフォルト)で識別されます。そのため、書き込みを実行することなく(必要に応じて低帯域幅のネットワーク経由で)、同じファイルコンテンツで異なるファイル名を持つ2つのリポジトリを同期できる必要があります。もちろん、それらをチェックサムするためにすべてのファイルを読む必要があります。


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でファイル名をパスにマッピングするハッシュテーブルを使用することを検討してください。


これは有望に見えますが、ファイルを移動するのですか、それとも単にシンボリックリンクを作成するのですか?
ダン

私はこれのほとんどを理解していると思います。しかし、grep行は何をしますか?で一致するファイルの完全なパスを見つけるだけdstlistですか?
ダン

@Dan:明らかにlnそれを使用することにより、シンボリックリンクが作成されます。あなたは使用することができるmvファイルを移動するのではなく、既存のものを上書きするには注意してください。また、ファイルを移動した後、空のディレクトリがあればクリーンアップすることもできます。はい、そのgrepコマンドはファイル名で終わる行を検索し、宛先ドライブでの完全なパスを明らかにします。
アレックス

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

私もこの問題に直面しました。md5sumベースのソリューションは、ファイルをwebdavマウント。webdav宛先でmd5sumの合計を計算することは、大規模なファイル操作も意味します。

最も移動されたファイルreorg_Remote_Dir_detect_moves.sh を検出しようとする小さなスクリプトを(githubで)作成し、リモートディレクトリを調整するためのいくつかのコマンドを使用して新しい一時シェルスクリプトを作成します。ファイル名のみを管理しているため、スクリプトは完璧なソリューションではありません。

安全のため、いくつかのファイルは無視されます。A)すべての側に同じ(同じ名前の)名前を持つファイル、およびB)リモート側にのみあるファイル。それらは無視され、スキップされます。

スキップされたファイルは、お好みの同期ツール(たとえば、 rsync, unison ...など)は、一時シェルスクリプトの実行後に使用する必要があります。

だから、私のスクリプトは誰かに役立つのでしょうか?その場合(より明確にするため)、3つのステップがあります。

  1. シェルスクリプトを実行する reorg_Remote_Dir_detect_moves.sh (githubで)
  2. これは一時的なシェルスクリプトを作成します/dev/shm/REORGRemoteMoveScript.sh=>これを実行して移動します(マウントすると高速になります)webdav
  3. 任意の同期ツールを実行します(例:rsync, unison...)

1

これが私の答えです。事前の警告として、私のスクリプト作成経験はすべてbashから得られるため、別のシェルを使用している場合、コマンド名または構文が異なる場合があります。

このソリューションでは、2つの別個のスクリプトを作成する必要があります。

この最初のスクリプトは、宛先ドライブ上のファイルを実際に移動する役割を果たします。

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

2番目のスクリプトは、最初のスクリプトが使用する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 '{}' \; 

基本的には、2つのスクリプトが連想配列を類似したものにしています $md5_map_file。最初に、ソースドライブ上のファイルのすべてのmd5が計算され、保存されます。md5に関連付けられているのは、ドライブのルートからの相対パスです。次に、宛先ドライブ上の各ファイルについて、md5が計算されます。このmd5を使用して、ソースドライブ上のそのファイルのパスが検索されます。コピー先ドライブ上のファイルは、ソースドライブ上のファイルのパスに一致するように移動されます。

このスクリプトにはいくつかの注意事項があります。

  • $ dstのすべてのファイルも$ srcにあると想定しています
  • $ dstからディレクトリを削除せず、ファイルを移動するだけです。私は現在、これを自動的に行う安全な方法を考えることができません

md5の計算には時間がかかります。すべてのコンテンツを実際に読み取る必要があります。Danがファイルが同一であると確信している場合、ディレクトリ構造内で単にファイルを移動するだけで非常に高速です(読み取りはできません)。したがって、md5sumここで使用するものではないようです。(rsync
ちなみに、

精度と速度のトレードオフです。単なるファイル名よりも高い精度を使用する方法を提供したかったのです。
クレドゥー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.