มีวิธีใดบ้างในการซิงค์โครงสร้างไดเรกทอรีเมื่อไฟล์ทั้งสองมีอยู่แล้ว?


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-soth-23-385/any


คุณแน่ใจหรือไม่ว่าชื่อนั้นเป็นตัวกำหนดเนื้อหาของไฟล์โดยเฉพาะมิฉะนั้นคุณควรพิจารณาเปรียบเทียบไฟล์ด้วยไฟล์เช็คซัมของพวกเขา
kasterma

คำตอบ:


11

ฉันจะไปกับกิลส์และชี้ให้คุณพร้อมเพรียงกันตามที่แนะนำโดยเจ hasen พร้อมเพรียงกัน 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)

ตอนนี้เราต้องการสร้างแผนที่: hash -> ชื่อไฟล์สำหรับแต่ละไดเรกทอรีแล้วใช้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

@Dan ขอโทษ ปัญหาคือฉันไม่รู้ว่าฉันสามารถตั้งสมมติฐานเกี่ยวกับชื่อไฟล์ของคุณได้อย่างไร โดยไม่ต้องเขียนสคริปต์สมมติฐานคือไม่สนุกโดยเฉพาะอย่างยิ่งในกรณีนี้ที่ฉันเลือกที่จะส่งออกชื่อไฟล์ไปยังแฟ้มdwheeler.com/essays/fixing-unix-linux-filenames.html
Janus

1
ซึ่งอาจเสียเวลามาก (และโหลด CPU) เนื่องจากไฟล์ขนาดใหญ่เหล่านี้ต้องอ่านอย่างสมบูรณ์เพื่อสร้างแฮช MD5 หากชื่อไฟล์และขนาดไฟล์ตรงกันอาจเป็นไปได้ที่จะทำการแฮชไฟล์ ควรทำการแฮชในขั้นตอนที่สองและสำหรับไฟล์ที่ตรงกับอย่างน้อยหนึ่งไฟล์ (ในดิสก์เดียวกัน) ในชื่อหรือขนาด
Hauke ​​Laging

คุณไม่จำเป็นต้องเรียงลำดับไฟล์ที่คุณใช้เป็นjoinอินพุตหรือไม่
cjm

8

มีโปรแกรมอรรถประโยชน์ที่เรียกว่าพร้อมเพรียง:

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

คำอธิบายจากเว็บไซต์:

Unison เป็นเครื่องมือซิงโครไนซ์ไฟล์สำหรับ Unix และ Windows อนุญาตให้เก็บแบบจำลองสองชุดของไฟล์และไดเรกทอรีที่จะจัดเก็บในโฮสต์ที่แตกต่างกัน (หรือดิสก์ที่แตกต่างกันในโฮสต์เดียวกัน), แก้ไขแยกต่างหากจากนั้นนำมาทันสมัยโดยเผยแพร่การเปลี่ยนแปลงในแต่ละแบบจำลองไปยังอีก

โปรดทราบว่าพร้อมเพรียงตรวจพบไฟล์ที่ถูกย้ายในการทำงานครั้งแรกหากอย่างน้อยหนึ่งรูทเป็นระยะไกลดังนั้นแม้ว่าคุณจะซิงโครไนซ์ไฟล์ในเครื่องให้ใช้ssh://localhost/path/to/dirเป็นหนึ่งในรูท


@Gilles: คุณแน่ใจหรือไม่ ฉันใช้พร้อมเพรียงสำหรับทุกอย่างและมักจะเห็นไฟล์ที่เปลี่ยนชื่อและ / หรือย้ายไปไกล คุณกำลังบอกว่ามันใช้งานได้กับไฟล์ที่ซิงค์แล้วซึ่งพร้อมเพรียงมีโอกาสบันทึกหมายเลข inode (หรือเทคนิคอื่น ๆ ที่ใช้)?
Janus

@ Janus: ขอบคุณสำหรับการแก้ไขความคิดเห็นของฉันผิดแน่นอน พร้อมเพรียงตรวจจับไฟล์ที่ถูกย้ายแม้ในการเริ่มต้น (มันไม่ได้ทำเมื่อรากทั้งสองอยู่ในพื้นที่ซึ่งเป็นสาเหตุที่มันไม่ได้ทำในการทดสอบของฉัน) ดังนั้นพร้อมเพรียงเป็นคำแนะนำที่ดีมาก
Gilles 'หยุดความชั่วร้าย'

@Gilles เป็นการดีที่จะทราบ - ดูเหมือนจะมีบางสถานที่ที่อัลกอริทึมแยกความแตกต่างระหว่างการซิงค์ในตัว จริง ๆ แล้วฉันไม่คิดว่ามันจะทำงานสำหรับการซิงค์ครั้งแรก +1 พร้อมกัน!
Janus

4

ใช้พร้อมเพรียงกันเป็นข้อเสนอแนะโดย hasen เจ ฉันทิ้งคำตอบนี้ไว้เป็นตัวอย่างของการเขียนสคริปต์ที่มีประโยชน์หรือเพื่อใช้กับเซิร์ฟเวอร์ที่ติดตั้งยูทิลิตี้พื้นฐานเท่านั้น


ฉันจะสมมติว่าชื่อไฟล์นั้นไม่ซ้ำกันตลอดทั้งลำดับชั้น ฉันจะสมมติว่าไม่มีชื่อไฟล์ใดที่มีการขึ้นบรรทัดใหม่และทรีไดเรกทอรีจะมีเฉพาะไดเรกทอรีและไฟล์ปกติ

  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/

โปรดทราบว่าฉันยังไม่ได้ทดสอบตัวอย่างในโพสต์นี้ ใช้ความเสี่ยงของคุณเอง กรุณารายงานข้อผิดพลาดใด ๆ ในความคิดเห็น


2

โดยเฉพาะอย่างยิ่งถ้ากำลังซิงค์จะเป็นประโยชน์คุณสามารถพยายามที่จะคิดออกGit-ภาคผนวก

มันค่อนข้างใหม่ ฉันไม่ได้ลองใช้ด้วยตัวเอง

ฉันสามารถแนะนำได้เพราะมันหลีกเลี่ยงการเก็บสำเนาไฟล์ที่สอง ... ซึ่งหมายความว่าจะต้องทำเครื่องหมายไฟล์เป็นแบบอ่านอย่างเดียว ("ล็อค") เช่นระบบควบคุมเวอร์ชันที่ไม่ใช่ Git

ไฟล์จะถูกระบุด้วยนามสกุลไฟล์ sha256sum + (โดยค่าเริ่มต้น) ดังนั้นควรซิงค์สอง repos ที่มีเนื้อหาไฟล์เหมือนกัน แต่ชื่อไฟล์ต่างกันโดยไม่ต้องทำการเขียน (และผ่านเครือข่ายที่มีแบนด์วิดท์ต่ำหากต้องการ) แน่นอนมันจะต้องอ่านไฟล์ทั้งหมดเพื่อที่จะตรวจสอบพวกเขา


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มีสัญลักษณ์ที่มีความหมายพิเศษสำหรับ regexps grepอยู่ในรายชื่อของไฟล์ที่ยังสามารถใช้เป็นจำนวนมากเวลาถ้ามีไฟล์จำนวนมาก คุณอาจลองแปลรหัสนี้เพื่อใช้ hashtable ซึ่งจะแมปชื่อไฟล์ไปยังเส้นทางเช่นใน Ruby


สิ่งนี้ดูมีแนวโน้ม - แต่มันย้ายไฟล์หรือเพียงแค่สร้าง symlink?
ด่าน

ฉันคิดว่าฉันเข้าใจเรื่องนี้เป็นส่วนใหญ่ แต่grepสายทำอะไร มันเพิ่งพบเส้นทางแบบเต็มของไฟล์ที่ตรงกันdstlistหรือไม่
Dan

@ แดน: เห็นได้ชัดว่าการใช้งานlnมันสร้าง symlink คุณอาจใช้mvเพื่อย้ายไฟล์ แต่ระวังการเขียนทับไฟล์ที่มีอยู่ นอกจากนี้คุณอาจต้องการล้าง dirs ที่ว่างเปล่าถ้ามีหลังจากย้ายไฟล์ออกไป ใช่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

ฉันยังประสบปัญหานี้ วิธีการแก้ปัญหาชั่ md5sum ไม่ทำงานสำหรับฉันเพราะฉันซิงค์ไฟล์ของฉันไปwebdavติด การคำนวณ md5sum เป็นจำนวนเงินบนwebdavปลายทางก็หมายถึงการทำงานของไฟล์ขนาดใหญ่

ฉันสร้างสคริปต์เล็ก ๆreorg_Remote_Dir_detect_moves.sh (บน github) ซึ่งพยายามตรวจจับไฟล์ที่ถูกย้ายมากที่สุดแล้วสร้างเชลล์สคริปต์ชั่วคราวใหม่พร้อมคำสั่งหลายคำสั่งเพื่อปรับรีโมตไดเร็กทอรี เนื่องจากฉันดูแลเฉพาะชื่อไฟล์สคริปต์จึงไม่ใช่โซลูชันที่สมบูรณ์แบบ

เพื่อความปลอดภัยไฟล์หลายไฟล์จะถูกละเว้น: A) ไฟล์ที่มีชื่อ (จุดเริ่มต้นเหมือนกัน) ในทุก ๆ ด้านและ B) ไฟล์ที่อยู่บนรีโมตเท่านั้น พวกเขาจะถูกละเว้นและข้ามไป

ไฟล์ที่ถูกข้ามจะถูกจัดการโดยเครื่องมือซิงค์ที่คุณต้องการ (เช่นrsync, unison... ) ซึ่งคุณต้องใช้หลังจากเรียกใช้เชลล์สคริปต์ชั่วคราว

ดังนั้นสคริปต์ของฉันอาจมีประโยชน์สำหรับใครบางคน? ถ้าเป็นเช่นนั้น (เพื่อให้ชัดเจนยิ่งขึ้น) มีสามขั้นตอน:

  1. รันเชลล์สคริปต์ reorg_Remote_Dir_detect_moves.sh (บน github)
  2. สิ่งนี้จะสร้างเชลล์ shell-script /dev/shm/REORGRemoteMoveScript.sh=> เรียกใช้สิ่งนี้เพื่อทำการเคลื่อนไหว (จะติดตั้งเร็วwebdav)
  3. เรียกใช้เครื่องมือซิงค์ที่คุณต้องการ (เช่นrsync, unison... )

1

นี่คือความพยายามของฉันที่คำตอบ เพื่อเป็นการเตือนล่วงหน้าประสบการณ์การเขียนสคริปต์ทั้งหมดของฉันมาจากการทุบตีดังนั้นหากคุณใช้เชลล์ที่แตกต่างกันชื่อคำสั่งหรือไวยากรณ์อาจแตกต่างกัน

โซลูชันนี้ต้องการสร้างสคริปต์แยกสองสคริปต์

สคริปต์แรกนี้รับผิดชอบการย้ายไฟล์บนไดรฟ์ปลายทาง

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

โดยทั่วไปสิ่งที่เกิดขึ้นเป็นสองสคริปต์ similuate $md5_map_fileอาเรย์ด้วย ก่อนอื่น md5s ทั้งหมดสำหรับไฟล์บนไดรฟ์ต้นทางจะถูกคำนวณและเก็บไว้ เชื่อมโยงกับ md5s เป็นเส้นทางสัมพัทธ์จากรูทของไดรฟ์ จากนั้นสำหรับแต่ละไฟล์บนไดรฟ์ปลายทางจะทำการคำนวณ md5 ด้วยการใช้ md5 นี้เส้นทางของไฟล์นั้นในไดรฟ์ต้นฉบับจะถูกค้นหา ไฟล์บนไดรฟ์ปลายทางนั้นจะถูกย้ายเพื่อให้ตรงกับพา ธ ของไฟล์บนไดรฟ์ต้นทาง

มีข้อแม้อยู่สองสามข้อพร้อมสคริปต์นี้:

  • มันถือว่าทุกไฟล์ใน $ dst นั้นอยู่ใน $ src ด้วย
  • มันไม่ได้ลบไดเรกทอรีใด ๆ จาก $ dst เพียงย้ายไฟล์ ขณะนี้ฉันไม่สามารถคิดวิธีที่ปลอดภัยในการทำสิ่งนี้ได้โดยอัตโนมัติ

ต้องใช้เวลานานในการคำนวณ md5: เนื้อหาทั้งหมดจะต้องถูกอ่านจริง แม้ว่า Dan จะแน่ใจว่าไฟล์เหมือนกัน แต่การย้ายไฟล์ในโครงสร้างไดเร็กทอรีนั้นเร็วมาก (ไม่อ่าน) ดังนั้นmd5sumดูเหมือนจะไม่เป็นสิ่งที่ใช้ที่นี่ (BTW rsyncมีโหมดที่ไม่ได้คำนวณ checksums)
imz - Ivan Zakharyaschev

มันเป็นการแลกเปลี่ยนระหว่างความแม่นยำและความเร็ว ฉันต้องการให้วิธีการที่ใช้ความแม่นยำระดับสูงกว่าชื่อไฟล์เพียงอย่างเดียว
cledoux
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.