Menggabungkan 2 pohon direktori di Linux tanpa menyalin?

35

Saya memiliki dua pohon direktori dengan tata letak yang sama, yaitu

.
 |-- 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

Saya ingin menggabungkan pohon direktori dir1 dan dir2 untuk membuat:

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

Saya tahu bahwa saya dapat melakukan ini menggunakan perintah "cp", tetapi saya ingin memindahkan file daripada menyalin, karena direktori aktual yang ingin saya gabungkan sangat besar dan berisi banyak file (jutaan). Jika saya menggunakan "mv" saya mendapatkan kesalahan "File exist" karena nama direktori yang saling bertentangan.

PEMBARUAN: Anda dapat berasumsi bahwa tidak ada file duplikat antara dua pohon direktori.

bajafresh4life
sumber
Apakah Anda yakin tidak ada duplikasi nama file antara dua folder? apa yang ingin Anda lakukan jika ada duplikat?
Zoredache
Jika Anda benar-benar memiliki jutaan file dalam satu direktori tunggal, Anda harus memecah file menjadi sub direktori terpisah untuk alasan kinerja - meskipun ini tidak relevan dengan pertanyaan aktual yang diajukan.
DrStalker

Jawaban:

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

Ini akan membuat hardlink daripada memindahkannya, Anda dapat memverifikasi bahwa mereka dipindahkan dengan benar, lalu, hapus dir1/dan dir2/.

karmawhore
sumber
9
Agak. Itu sebenarnya tidak menduplikasi penggunaan disk, itu hanya menciptakan pointer lain ke sebongkah disk yang sama, dan tidak benar-benar 'menyalin' data apa pun. (Lihat en.wikipedia.org/wiki/Hard_links ) Namun, itu harus melakukan operasi itu sekali per file. Tetapi pada dasarnya itulah yang dilakukan oleh semua jawaban ini, karena Anda tidak bisa hanya memindahkan satu direktori.
Christopher Karel
1
Karena tidak memiliki overhead menyalin file, ini adalah solusi yang dapat diterima.
Tobu
2
Ini hanya berfungsi jika mereka berada di sistem file yang sama. Apakah rsync dengan opsi hapus akan bergerak jika mereka berada di sistem file yang sama? (artinya, ubah saja info direktori, tetapi jangan pindahkan file).
Ronald Pottol
1
rsync akan menyalin, lalu menghapus jika melintasi sistem file.
karmawhore
5
Satu peringatan: jadikan --link-destjalur absolut, atau relatif terhadap merged/; atau itu akan menyalin.
Tobu
21

Sangat aneh tidak ada yang mencatat bahwa cpmemiliki opsi -l:

-l, --link
       file tautan keras alih-alih menyalin

Anda dapat melakukan sesuatu seperti

% mkdir bergabung
% cp -rl dir1 / * dir2 / * bergabung
% rm -r dir *
% penggabungan pohon 
menggabungkan
├── a
│ ├── file1.txt
│ ├── file2.txt
│ ├── file5.txt
│ └── file6.txt
├── b
│ ├── file3.txt
│ ├── file7.txt
│ └── file8.txt
└── c
    ├── file10.txt
    ├── file4.txt
    └── file9.txt

13 direktori, 0 file
Maximilian
sumber
Ini tidak berfungsi di hard drive yang berbeda ...
Alex Leach
4
Lebih tepat untuk mengatakan bahwa itu tidak bekerja di seluruh sistem file, karena sistem file dapat menjangkau beberapa hard drive. Juga, jika yang diinginkan op adalah untuk menghindari menyalin file, itu adalah hal yang baik yang cp -ltidak bekerja di seluruh sistem file.
lvella
2
Anda mungkin ingin menggunakan cp -a(sinonim dengan cp -RPp) untuk menjaga semua atribut file dan menghindari symlink berikut: di sini perintah menjadi cp -al dir1/* dir2/* merge.
tricasse
5

Anda dapat menggunakan rename (alias nama samaran, dari paket perl) untuk itu. Berhati-hatilah bahwa nama tersebut tidak merujuk pada perintah yang saya jelaskan di luar debian / ubuntu (meskipun itu file perl portabel tunggal jika Anda memerlukannya).

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

Anda juga memiliki opsi untuk menggunakan vidir (dari moreutils), dan mengedit jalur file dari editor teks pilihan Anda.

Tobu
sumber
3

Aku seperti rsync dan prename solusi, tetapi jika Anda benar-benar ingin membuat mv melakukan pekerjaan dan

  • temuan Anda tahu -print0dan -depth,
  • xargs Anda tahu -0,
  • Anda memiliki printf ,

maka dimungkinkan untuk menangani sejumlah besar file yang mungkin memiliki spasi kosong acak dalam namanya, semua dengan skrip shell Bourne-style:

#!/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
Chris Johnsen
sumber
Anda dapat memberi tahu xargs untuk membatasi inputnya ke baris baru dan melewati terjemahan. misalnya yang berikut ini akan menemukan dan menghapus semua file torrent Anda di bawah direktori saat ini, bahkan yang dengan karakter unicode atau beberapa tindakan gila-gilaan lainnya. find . -name '*.torrent' | xargs -d '\n' rm
PRS
2

Paksaan 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

Tes melakukan ini

# 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
David J. Liszewski
sumber
2
OP menentukan jutaan file, yang kemungkinan akan merusak konstruksi ini. Juga, itu tidak akan menangani nama file dengan spasi, baris baru, dll.
Chris Johnsen
0

Saya harus melakukan ini beberapa kali untuk pohon kode sumber pada berbagai tahap pengembangan. Solusi saya adalah menggunakan Git dengan cara berikut:

  1. Buat repositori git dan tambahkan semua file dari dir1.
  2. Melakukan
  3. Hapus semua file dan salin file dari dir2
  4. Melakukan
  5. Lihat perbedaan antara dua poin komit dan buat keputusan yang hati-hati tentang bagaimana saya ingin menggabungkan hasilnya.

Anda dapat membuatnya dengan bercabang dan sebagainya, tetapi ini adalah ide umum. Dan Anda memiliki lebih sedikit rasa takut untuk memasukkannya karena Anda memiliki snapshot lengkap dari setiap negara.


sumber