ผสาน 2 ไดเรกทอรีต้นไม้ใน Linux โดยไม่คัดลอก?


35

ฉันมีต้นไม้ไดเรกทอรีสองต้นที่มีเค้าโครงคล้ายกันเช่น

.
 |-- dir1
 |   |-- a
 |   |   |-- file1.txt
 |   |   `-- file2.txt
 |   |-- b
 |   |   `-- file3.txt
 |   `-- c
 |       `-- file4.txt
 `-- dir2
     |-- a
     |   |-- file5.txt
     |   `-- file6.txt
     |-- b
     |   |-- file7.txt
     |   `-- file8.txt
     `-- c
         |-- file10.txt
         `-- file9.txt

ฉันต้องการรวมต้นไม้ไดเรกทอรี dir1 และ dir2 เพื่อสร้าง:

 merged/
 |-- a
 |   |-- file1.txt
 |   |-- file2.txt
 |   |-- file5.txt
 |   `-- file6.txt
 |-- b
 |   |-- file3.txt
 |   |-- file7.txt
 |   `-- file8.txt
 `-- c
     |-- file10.txt
     |-- file4.txt
     `-- file9.txt

ฉันรู้ว่าฉันสามารถทำได้โดยใช้คำสั่ง "cp" แต่ฉันต้องการย้ายไฟล์แทนการคัดลอกเพราะไดเรกทอรีจริงที่ฉันต้องการผสานมีขนาดใหญ่มากและมีไฟล์จำนวนมาก (ล้าน) หากฉันใช้ "mv" ฉันจะได้รับข้อผิดพลาด "ไฟล์มีอยู่" เนื่องจากชื่อไดเรกทอรีที่ขัดแย้งกัน

UPDATE: คุณสามารถสันนิษฐานได้ว่าไม่มีไฟล์ที่ซ้ำกันระหว่างแผนผังไดเรกทอรีทั้งสอง


คุณแน่ใจหรือไม่ว่าไม่มีชื่อไฟล์ซ้ำกันระหว่างสองโฟลเดอร์ คุณต้องการเกิดอะไรขึ้นถ้ามีรายการที่ซ้ำกัน
Zoredache

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

คำตอบ:


28
rsync -ax --link-dest=dir1/ dir1/ merged/
rsync -ax --link-dest=dir2/ dir2/ merged/

นี้จะสร้าง hardlinks มากกว่าย้ายพวกเขาคุณสามารถตรวจสอบว่าพวกเขาถูกย้ายอย่างถูกต้องแล้วลบและdir1/dir2/


9
ชนิดของ มันไม่ได้เป็นการจำลองการใช้งานดิสก์ใด ๆ เพียงแค่สร้างตัวชี้อื่นไปยังดิสก์ก้อนใหญ่และไม่ได้ 'คัดลอก' ข้อมูลใด ๆ (ดูen.wikipedia.org/wiki/Hard_links ) อย่างไรก็ตามจะต้องดำเนินการนั้นหนึ่งครั้งต่อไฟล์ แต่นั่นคือสิ่งที่ทุกคำตอบจบลงเนื่องจากคุณไม่สามารถย้ายไดเรกทอรีเดียวได้
Christopher Karel

1
เนื่องจากมันไม่มี io โอเวอร์เฮดของการคัดลอกไฟล์จึงเป็นทางออกที่ดีที่สุด
Tobu

2
วิธีนี้ใช้ได้เฉพาะในกรณีที่อยู่ในระบบไฟล์เดียวกันเท่านั้น rsync พร้อมกับตัวเลือกการลบจะย้ายหรือไม่หากอยู่ในระบบไฟล์เดียวกัน (กล่าวคือเพียงแค่เปลี่ยนข้อมูลไดเรกทอรี แต่ไม่ย้ายไฟล์)
Ronald Pottol

1
rsync จะคัดลอกแล้วลบหากผ่านระบบไฟล์
karmawhore

5
หนึ่ง caveat: ทำให้--link-destเส้นทางแน่นอนหรือสัมพันธ์กับmerged/; หรือจะคัดลอก
Tobu

21

มันแปลกไม่มีใครสังเกตเห็นว่าcpมีตัวเลือก-l:

-l, - ลิงก์
       ไฟล์ฮาร์ดลิงก์แทนการคัดลอก

คุณสามารถทำสิ่งที่ชอบ

% mkdir ผสาน
% cp -rl dir1 / * dir2 / * ผสาน
% rm -r dir *
รวมต้นไม้ 
ผสาน
├──
1 ├── file1.txt
2 ├── file2.txt
5 ├── file5.txt
6 └── file6.txt
├── b
│├── file3.txt
7 ├── file7.txt
8 └── file8.txt
└── c
    ├── file10.txt
    ├── file4.txt
    └── file9.txt

13 ไดเรกทอรี, 0 ไฟล์

นี้ไม่ได้ทำงานในฮาร์ดไดรฟ์ที่แตกต่างกัน ...
อเล็กซ์กรอง

4
มันถูกต้องมากกว่าที่จะบอกว่ามันไม่สามารถใช้งานข้ามระบบไฟล์ได้เนื่องจากระบบไฟล์สามารถขยายข้ามฮาร์ดไดรฟ์หลาย ๆ ตัวได้ นอกจากนี้ถ้าสิ่งที่ op ต้องการคือการหลีกเลี่ยงการคัดลอกไฟล์มันเป็นสิ่งที่ดีที่cp -lไม่สามารถใช้งานข้ามระบบไฟล์ได้
lvella

2
คุณอาจต้องการใช้cp -a(คำพ้องความหมายcp -RPp) เพื่อเก็บแอตทริบิวต์ทั้งหมดของไฟล์cp -al dir1/* dir2/* mergeไว้
tricasse

5

คุณสามารถใช้การเปลี่ยนชื่อ (aka prename จากแพ็คเกจ perl) สำหรับสิ่งนั้น ระวังว่าชื่อไม่จำเป็นต้องอ้างถึงคำสั่งที่ฉันอธิบายนอกเดเบียน / อูบุนตู (แม้ว่าจะเป็นไฟล์ Perl แบบพกพาไฟล์เดียวหากคุณต้องการ)

mv -T dir1 merged
rename 's:^dir2/:merged/:' dir2/* dir2/*/*
find dir2 -maxdepth 1 -type d -empty -delete

คุณยังมีตัวเลือกในการใช้ vidir (จาก moreutils) และแก้ไขพา ธ ไฟล์จากโปรแกรมแก้ไขข้อความที่คุณต้องการ


3

ฉันชอบโซลูชั่นrsyncและprenameแต่ถ้าคุณอยากทำให้mvทำงานและ

  • คุณหารู้-print0และ-depth,
  • คุณxargsรู้-0,
  • คุณได้printf ,

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

#!/bin/sh

die() {
    printf '%s: %s\n' "${0##*/}" "$*"
    exit 127
}
maybe=''
maybe() {
    if test -z "$maybe"; then
        "$@"
    else
        printf '%s\n' "$*"
    fi
}

case "$1" in
    -h|--help)
        printf "usage: %s [-n] merge-dir src-dir [src-dir [...]]\n" "${0##*/}"
        printf "\n    Merge the <src-dir> trees into <merge-dir>.\n"
        exit 127
    ;;
    -n|--dry-run)
        maybe=NotRightNow,Thanks.; shift
    ;;
esac

test "$#" -lt 2 && die 'not enough arguments'

mergeDir="$1"; shift

if ! test -e "$mergeDir"; then
    maybe mv "$1" "$mergeDir"
    shift
else
    if ! test -d "$mergeDir"; then
        die "not a directory: $mergeDir"
    fi
fi

xtrace=''
case "$-" in *x*) xtrace=yes; esac
for srcDir; do
    (cd "$srcDir" && find . -print0) |
    xargs -0 sh -c '

        maybe() {
            if test -z "$maybe"; then
                "$@"
            else
                printf "%s\n" "$*"
            fi
        }
        xtrace="$1"; shift
        maybe="$1"; shift
        mergeDir="$1"; shift
        srcDir="$1"; shift
        test -n "$xtrace" && set -x

        for entry; do
            if test -d "$srcDir/$entry"; then
                maybe false >/dev/null && continue
                test -d "$mergeDir/$entry" || mkdir -p "$mergeDir/$entry"
                continue
            else
                maybe mv "$srcDir/$entry" "$mergeDir/$entry"
            fi
        done

    ' - "$xtrace" "$maybe" "$mergeDir" "$srcDir"
    maybe false >/dev/null ||
    find "$srcDir" -depth -type d -print0 | xargs -0 rmdir
done

คุณสามารถบอกให้ xargs กำหนดขอบเขตการป้อนข้อมูลให้กับ newline และข้ามการแปล ตัวอย่างต่อไปนี้จะค้นหาและลบไฟล์ torrent ทั้งหมดของคุณภายใต้ไดเรกทอรีปัจจุบันแม้แต่ไฟล์ที่มีอักขระ Unicode หรือ tomfoolery อื่น ๆ find . -name '*.torrent' | xargs -d '\n' rm
PRS

2

กำลังดุร้าย bash

#! /bin/bash

for f in $(find dir2 -type f)
do
  old=$(dirname $f)
  new=dir1${old##dir2}
  [ -e $new ] || mkdir $new
  mv $f $new
done

ทดสอบทำสิ่งนี้

# setup 
for d in dir1/{a,b,c} dir2/{a,b,c,d} ; do mkdir -p $d ;done
touch dir1/a/file{1,2} dir1/b/file{3,4} dir2/a/file{5,6} dir2/b/file{7,8} dir2/c/file{9,10} dir2/d/file11

# do it and look
$ find dir{1,2} -type f
dir1/a/file1
dir1/a/file2
dir1/a/file5
dir1/a/file6
dir1/b/file3
dir1/b/file7
dir1/b/file8
dir1/c/file4
dir1/c/file9
dir1/c/file10
dir1/d/file11

2
OP ระบุไฟล์เป็นล้านไฟล์ซึ่งน่าจะทำให้โครงสร้างนี้พัง นอกจากนี้ก็จะไม่ต้องจัดการชื่อไฟล์ที่มีช่องว่าง, การขึ้นบรรทัดใหม่ ฯลฯ ..
คริสจอห์นสัน

0

ฉันต้องทำสิ่งนี้หลายครั้งสำหรับซอร์สโค้ดทรีในขั้นตอนต่าง ๆ ของการพัฒนา ทางออกของฉันคือการใช้ Git ด้วยวิธีต่อไปนี้:

  1. สร้างที่เก็บ git และเพิ่มไฟล์ทั้งหมดจาก dir1
  2. ผูกมัด
  3. ลบไฟล์ทั้งหมดและคัดลอกไฟล์จาก dir2
  4. ผูกมัด
  5. ดูความแตกต่างระหว่างสองจุดกระทำและตัดสินใจอย่างรอบคอบเกี่ยวกับวิธีที่ฉันต้องการผสานผลลัพธ์

คุณสามารถกลเม็ดกับการแยกและอื่น ๆ แต่นี่คือความคิดทั่วไป และคุณมีความกลัวน้อยลงเกี่ยวกับการบรรจุเพราะคุณมีภาพรวมที่สมบูรณ์ของแต่ละรัฐ

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