Dosyalar zaten iki taraftayken dizin yapısını senkronize etmenin bir yolu var mı?


24

Aynı dosyalara sahip iki sürücüm var, ancak dizin yapısı tamamen farklı.

Hedef taraftaki tüm dosyaları kaynak tarafın yapısına uygun olacak şekilde 'taşımanın' herhangi bir yolu var mı? Belki bir senaryo ile?

Örneğin, A sürücüsünde şunlar bulunur:

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

B sürücüsünün ise:

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

Söz konusu dosyalar çok büyük (800GB), bu yüzden onları tekrar kopyalamak istemiyorum; Sadece gerekli dizinleri oluşturarak ve dosyaları taşıyarak yapıyı senkronize etmek istiyorum.

Hedefteki her kaynak dosyayı bulan ve daha sonra gerektiğinde yaratan bir dizine götürecek özyinelemeli bir komut dosyası düşünüyordum. Ama - bu yeteneklerimin ötesinde!

Başka bir zarif çözüm burada verildi: /superuser/237387/any-way-to-sync-directory-structure-when-the-files-are-already-both-sides/238086


Adın bir dosyanın içeriğini benzersiz bir şekilde belirlediğinden emin misiniz, aksi takdirde dosyaları sağlama toplamlarıyla karşılaştırmayı düşünmelisiniz.
kasterma

Yanıtlar:


11

Gilles'la gideceğim ve seni hasen j'nin önerdiği gibi Unison'a yönlendireceğim . Unison, DropBox'tan 20 yıl önce DropBox'tı. Çok sayıda insanın (kendim dahil) her gün kullandığı katı kod - öğrenmeye değer. Yine de, joinalabileceği tüm tanıtımlara ihtiyacı var :)


Bu sadece yarım cevap, ama işe geri dönmem gerek :)

Temel olarak, joinsadece bunu yapan az bilinen programı göstermek istedim : bir alanda iki masayı birleştirir.

İlk önce, boşluk içeren dosya adlarını içeren bir test durumu oluşturun:

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

(bazı dizin ve / veya dosya isimlerini düzenleyiniz new)

Şimdi, her bir dizin için bir harita oluşturmak istiyoruz: hash -> dosya adı ve ardından joinaynı hash ile dosyaları eşleştirmek için kullanıyoruz . Haritayı oluşturmak için aşağıdakileri yazın makemap.sh:

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

makemap.sh 'hash "dosyaadı"' şeklindeki satırları içeren bir dosyayı dağıtıyor, bu yüzden ilk sütuna katıldık:

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

Bu moves.txtşuna benzeyen üretir :

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

Bir sonraki adım, hamleleri gerçekten yapmak olacaktır, ancak girişimlerim alıntı yapmakta zorlandı ... mv -ive işe yaramalı mkdir -p.


Üzgünüm, bunların hiçbirini anlamıyorum!
Dan

1
joingerçekten ilginç. Dikkatimi çektiğin için teşekkür ederim.
Steven D

@Dan. Üzgünüm. Sorun şu ki, dosya adlarınız hakkında ne gibi varsayımlar yapabileceğimi bilmiyorum. Varsayımsız komut dosyası yazmak eğlenceli değildir, özellikle de dosya adlarını dwheeler.com/essays/fixing-unix-linux-filenames.html dosyasına vermeyi seçtiğim bu durumda .
Janus

1
Bu muhtemelen çok fazla zaman harcıyor (ve CPU yükü) çünkü bu devasa dosyaların MD5 karmalarını oluşturmak için tamamen okunması gerekiyor. Dosya adı ve dosya boyutu eşleşiyorsa, büyük olasılıkla dosyaların hashini overkill olur. Karma, ikinci bir adımda ve yalnızca ad veya boyutta en az bir tanesiyle aynı dosyalarda (aynı diskte) eşleşen dosyalar için yapılmalıdır.
Hauke,

joinGirdi olarak kullandığınız dosyaları sıralamanıza gerek yok mu?
cjm

8

Unison adında bir yardımcı program var:

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

Siteden açıklama:

Unison, Unix ve Windows için bir dosya senkronizasyon aracıdır. Bir dosya ve dizin koleksiyonunun iki kopyasının, farklı ana bilgisayarlarda (veya aynı ana bilgisayardaki farklı disklerde) depolanmasına, ayrı olarak değiştirilmesine ve daha sonra her bir çoğaltmadaki değişiklikleri diğerine geçirerek güncellenmesine olanak tanır.

Unison'un taşınan dosyaları yalnızca ilk çalıştırmada köklerden en az birinin uzak olması durumunda algıladığını unutmayın; bu nedenle, yerel dosyaları senkronize ediyor olsanız bile ssh://localhost/path/to/dir, köklerden birini kullanın .


@Gilles: Emin misiniz? Her şey için unison kullanıyorum ve sık sık yeniden adlandırılmış ve / veya uzak bir yere taşınan dosyaları tespit ettiğini görüyorum. Bunun sadece, unison'ın inode numaralarını kaydetme şansına sahip olduğu (veya kullandığı diğer numaralar) zaten senkronize edilmiş dosyalar için çalıştığını mı söylüyorsunuz?
Janus

@ Joanus: Düzeltme için teşekkürler, yorumum gerçekten yanlıştı. Unison, ilk çalıştırmada bile taşınan dosyaları algılar. (Her iki kök de yerel olduğunda bunu yapmaz, bu yüzden testimde yapmadı.) Yani birleştirmek çok iyi bir öneri.
Gilles 'SO- kötülük' dur '28

@Gilles. Bilmek güzel - algoritmanın yerel ve uzak senkronizasyon arasında ayrım yaptığı epeyce yer var gibi görünüyor. Aslında ilk senkronizasyon için işe yarayacağını düşünmedim. Unison için +1!
Janus

4

Kullanım Unison olarak hasen j önerdiği . Bu cevabı potansiyel olarak yararlı bir komut dosyası örneği olarak veya yalnızca temel hizmetlerin kurulu olduğu bir sunucuda kullanım için bırakıyorum.


Dosya adlarının hiyerarşi boyunca benzersiz olduğunu varsayacağım. Ayrıca hiçbir dosya adının yeni bir satır içermediğini ve dizin ağaçlarının yalnızca dizinleri ve normal dosyaları içerdiğini varsayalım.

  1. Önce kaynak tarafındaki dosya adlarını toplayın.

    (cd /A && find . \! -type d) >A.find
  2. Sonra dosyaları hedef taraftaki yerine yerleştirin. İlk önce, hedef tarafta düzleştirilmiş bir dosyalar ağacı oluşturun. Eski hiyerarşide sabit bağlantıları korumak istiyorsanız lnyerine kullanın mv.

    mkdir /B.staging /B.new
    find /B.old -type f -exec sh -c 'mv -- "$@" "$0"' /B.staging {} +
  3. Hedefte bazı dosyalar eksikse, benzer şekilde düzleştirilmiş bir dosya oluşturun /A.stagingve verileri kaynaktan hedefe kopyalamak için rsync kullanın.

    rsync -au /A.staging/ /B.staging/
  4. Şimdi dosyaları yerine yerleştirin.

    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/$_: $!"
    '

    eşdeğer bir:

    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. Son olarak, dizinlerin meta verilerini önemsiyorsanız, dosyaları önceden mevcutsa rsync ile çağırın.

    rsync -au /A/ /B.new/

Bu gönderideki pasajları test etmediğimi unutmayın. Kullanım kendi sorumluluğunuzdadır. Lütfen bir yorumdaki herhangi bir hatayı bildirin.


2

Özellikle devam eden senkronizasyon faydalı olursa, git-eki bulmaya çalışabilirsiniz .

Nispeten yeni; Kendim kullanmayı denemedim.

Bunu önerebilirim çünkü dosyaların ikinci bir kopyasını saklamaktan kaçınıyor ... bu, dosyaları Git'in olmayan bazı sürüm kontrol sistemleri gibi salt okunur ("kilitli") olarak işaretlemesi gerektiği anlamına geliyor.

Dosyalar sha256sum + dosya uzantısı ile tanımlanır (varsayılan olarak). Bu yüzden, iki repoyu aynı dosya içeriğiyle, ancak farklı dosya adlarıyla, yazma yapmak zorunda kalmadan (ve istenirse düşük bant genişliğine sahip bir ağ üzerinden) senkronize edebilmelidir. Tabii ki, sağlama toplamı için tüm dosyaları okumak zorunda kalacak.


1

Böyle bir şey hakkında nasıl:

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

Bu, senkronize etmek istediğiniz dosyaların adlarının tüm sürücüde benzersiz olduğunu varsayar: aksi halde tam otomatik hale getirilmesinin imkanı yoktur (ancak, daha fazla olması durumunda hangi dosyayı seçeceğini seçmek için bir istekte bulunabilirsiniz).

Yukarıdaki komut dosyası basit durumlarda çalışır, ancak nameregexps için özel bir anlamı olan simgeler içeriyorsa başarısız olabilir . Çok grepsayıda dosya varsa, dosya listesinde de çok zaman alabilir. Bu kodu, dosya adlarını yollarla eşleştirecek bir karma tablo kullanmak için örneğin Ruby'de çevirmeyi düşünebilirsiniz.


Bu umut verici görünüyor - ama dosyaları taşıyor mu, yoksa sadece sembolik bağlantılar mi yaratıyor?
Dan

Sanırım bunun çoğunu anlıyorum; ama grephat ne yapar ? Eşleşen dosyanın tam yolunu içinde buluyor dstlistmu?
Dan

@Dan: görünüşe göre kullanımı ile lnsembolik bağlantılar oluşturur. mvDosyaları taşımak için istihdam edebilirsiniz , ancak varolanların üzerine yazmaktan kaçının. Ayrıca, dosyaları uzaklaştırdıktan sonra, varsa boş dizinleri temizlemek isteyebilirsiniz. Evet, bu grepkomut dosya isminde biten bir satırı arar, böylece hedef sürücüde tam yolunu gösterir.
alex

1

Temel dosya adlarının ağaçlarda benzersiz olduğu varsayılarak, oldukça basittir:

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

Eski boş dizinleri temizlemek istiyorsanız, şunu kullanın:

find B -depth -type d -delete

1

Ben de bu problemle karşılaştım. Md5sum tabanlı çözüm benim için işe yaramadı, çünkü dosyalarımı bir webdavdağıma senkronize ettim . webdavHedef üzerindeki md5sum toplamlarının hesaplanması aynı zamanda büyük dosya işlemleri anlamına gelir.

En çok taşınan dosyaları reorg_Remote_Dir_detect_moves.sh tespit etmeye çalışan küçük bir komut dosyası (github'da) yaptım ve daha sonra uzak dizini ayarlamak için birkaç komutla yeni bir geçici kabuk betiği yarattım. Yalnızca dosya adlarıyla ilgilendiğim için, komut dosyası mükemmel bir çözüm değil.

Güvenlik için, birkaç dosya dikkate alınmayacaktır: A) Her iki tarafta da aynı (aynı başlangıçta) isimleri olan dosyalar ve B) Sadece uzaktaki dosyalar. Yok sayılacaklar ve atlanacaklar.

Atlanan dosyalar daha sonra rsync, unisongeçici kabuk betiğini çalıştırdıktan sonra kullanmanız gereken tercih ettiğiniz senkronizasyon aracı (örneğin , ...) tarafından ele alınacaktır .

Belki de senaryom birileri için faydalıdır? Eğer öyleyse (daha net yapmak için) üç adım vardır:

  1. Kabuk betiğini çalıştırın reorg_Remote_Dir_detect_moves.sh (github'da)
  2. Bu geçici bir kabuk betiği yaratacak /dev/shm/REORGRemoteMoveScript.sh=> hamle yapmak için bunu çalıştırın (hızlı monte edilecek webdav)
  3. Tercih ettiğiniz senkronizasyon aracını çalıştırın (örneğin rsync, unison, ...)

1

İşte bir cevabım girişimi. Bir uyarıcı olarak, tüm senaryo deneyimim bash'tan geliyor, bu nedenle farklı bir kabuk kullanıyorsanız, komut adları veya sözdizimi farklı olabilir.

Bu çözüm iki ayrı komut dosyası oluşturmayı gerektirir.

Bu ilk komut dosyası, dosyaları hedef sürücüde taşımaktan sorumludur.

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

İkinci komut dosyası, ilk komut dosyası tarafından kullanılan md5 harita dosyasını oluşturur ve ardından ilk komut dosyasını hedef sürücüdeki her dosyada çağırır.

# 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 '{}' \; 

Temel olarak, devam eden şey, iki betik, ilişkisel bir diziyi simüle eder $md5_map_file. İlk olarak, kaynak sürücüdeki dosyaların tüm md5'leri hesaplanır ve saklanır. Md5'lerle ilişkili, sürücünün kök dizinindeki göreceli yollardır. Ardından, hedef sürücüdeki her dosya için md5 hesaplanır. Bu md5 kullanılarak, kaynak sürücüdeki o dosyanın yolu aranır. Hedef sürücüdeki dosya daha sonra kaynak sürücüdeki dosyanın yoluyla eşleşecek şekilde taşınır.

Bu senaryoda bir kaç uyarı var:

  • $ Dst’deki her dosyanın $ src’de olduğunu varsayar.
  • Hiçbir dizini $ dst'den kaldırmaz, sadece dosyaları taşır. Şu anda bunu otomatik olarak yapmanın güvenli bir yolunu düşünemiyorum

MD5'leri hesaplamak biraz zaman almalı: tüm içerik gerçekten okunmalı. Dan, dosyaların aynı olduğundan emin olsa da, onları dizin yapısında taşımak çok hızlıdır (okuma yok). Yani, md5sumburada kullanılacak şey gibi görünüyor. (BTW, rsyncsağlama toplamlarını hesaplamadığı bir moda sahiptir.)
imz - Ivan Zakharyaschev

Doğruluk ve hız arasındaki fark. Basit dosya isimlerinden daha yüksek doğruluk derecesine sahip bir yöntem sunmak istedim.
Cledoux
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.