web-dev-qa-db-ja.com

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

同じファイルを持つ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

25
Dan

hasen j で提案されているように、Gillesと一緒に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 -imkdir -pは便利です。

11
Janus

Unisonというユーティリティがあります。

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

サイトからの説明:

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

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

8
hasen

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


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

  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.stagingを作成し、rsyncを使用してソースから宛先にデータをコピーします。

    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/
    

この記事のスニペットはテストしていません。自己責任。コメントでエラーを報告してください。

特に、進行中の同期が役立つ場合は、 git-annex を理解してみてください。

それは比較的新しいです。自分で使ったことはありません。

Git以外の特定のバージョン管理システムのように、ファイルを読み取り専用(「ロック」)としてマークする必要があることを意味します。

ファイルは、sha256sum +ファイル拡張子(デフォルト)で識別されます。したがって、書き込みを実行せずに(および必要に応じて低帯域幅ネットワークを介して)、ファイルの内容は同じでファイル名が異なる2つのリポジトリを同期できるはずです。もちろん、それらをチェックサムするためにすべてのファイルを読み取る必要があります。

2
sourcejedi

このようなものはどうですか:

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で。

1
alex

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

私は小さなスクリプトを作成しました reorg_Remote_Dir_detect_moves.sh (github上)これはmost移動されたファイルを検出しようとしていますリモートディレクトリを調整するいくつかのコマンドを含む新しい一時シェルスクリプトを作成します。ファイル名のみを処理するため、スクリプトは完全な解決策ではありません。

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

スキップされたファイルは、一時的なシェルスクリプトを実行した後に使用する必要のある同期ツール(rsync, unisonなど)によって処理されます。

それで、私のスクリプトは誰かにとって有用でしょうか?その場合(より明確にするため)、3つのステップがあります。

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

基本ファイル名がツリー内で一意であると仮定すると、それはかなり簡単です。

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
Uditha Desilva

これが私の答えです。事前警告として、私のスクリプトの経験はすべて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からディレクトリを削除するのではなく、ファイルを移動するだけです。私は現在、これを自動的に行う安全な方法を考えることができません
1
cledoux