Hợp nhất 2 cây thư mục trong Linux mà không cần sao chép?


35

Tôi có hai cây thư mục với bố cục tương tự, tức là

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

Tôi muốn hợp nhất các cây thư mục dir1 và dir2 để tạo:

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

Tôi biết rằng tôi có thể thực hiện việc này bằng lệnh "cp", nhưng tôi muốn di chuyển các tệp thay vì sao chép, vì các thư mục thực tế tôi muốn hợp nhất thực sự lớn và chứa rất nhiều tệp (hàng triệu). Nếu tôi sử dụng "mv", tôi gặp lỗi "Tệp tồn tại" do tên thư mục xung đột.

CẬP NHẬT: Bạn có thể giả sử rằng không có tệp trùng lặp giữa hai cây thư mục.


Bạn có chắc chắn không có sự trùng lặp tên tệp giữa hai thư mục? Bạn muốn điều gì xảy ra nếu có sự trùng lặp?
Zoredache

Nếu bạn thực sự có hàng triệu tệp trong một thư mục thì bạn nên xem xét việc chia các tệp thành các thư mục phụ riêng biệt vì lý do hiệu suất - mặc dù điều này không liên quan đến câu hỏi thực tế.
DrStalker

Câu trả lời:


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

Điều này sẽ tạo liên kết cứng thay vì di chuyển chúng, bạn có thể xác minh rằng chúng đã được di chuyển chính xác, sau đó, loại bỏ dir1/dir2/.


9
Loại. Nó không thực sự sao chép bất kỳ việc sử dụng đĩa nào, nó chỉ đơn giản tạo ra một con trỏ khác vào cùng một ổ đĩa và không thực sự 'sao chép' bất kỳ dữ liệu nào. (Xem en.wikipedia.org/wiki/Hard_links ) Tuy nhiên, nó phải thực hiện thao tác đó một lần cho mỗi tệp. Nhưng đó thực chất là những gì tất cả những câu trả lời này đang thực hiện, vì bạn không thể di chuyển một thư mục duy nhất.
Christopher Karel

1
Vì nó không có chi phí sao chép io, đây là một giải pháp hoàn toàn chấp nhận được.
Tobu

2
Điều này chỉ hoạt động nếu chúng nằm trên cùng một hệ thống tập tin. Rsync với tùy chọn xóa sẽ di chuyển nếu chúng nằm trên cùng một hệ thống tệp? (có nghĩa là, chỉ cần thay đổi thông tin thư mục, nhưng không di chuyển tệp).
Ronald Pottol

1
rsync sẽ sao chép, sau đó xóa nếu nó đi qua các hệ thống tập tin.
karmawhore

5
Một cảnh báo: làm cho --link-destđường dẫn tuyệt đối, hoặc liên quan đến merged/; hoặc nó sẽ sao chép.
Tobu

21

Thật lạ khi không ai lưu ý rằng cpcó tùy chọn -l:

-l, - liên kết
       tập tin liên kết cứng thay vì sao chép

Bạn có thể làm một cái gì đó như

% mkdir hợp nhất
% cp -rl dir1 / * dir2 / * hợp nhất
% rm -r thư mục *
% cây hợp nhất 
hợp nhất
├── a
├── file1.txt
├── file2.txt
├── file5.txt
└── file6.txt
├── b
├── file3.txt
├── file7.txt
└── file8.txt
└── c
    ├── file10.txt
    ├── file4.txt
    └── file9.txt

13 thư mục, 0 tập tin

Điều này không hoạt động trên các ổ đĩa cứng khác nhau ...
Alex Leach

4
Sẽ đúng hơn khi nói rằng nó không hoạt động trên các hệ thống tệp, bởi vì các hệ thống tệp có thể trải rộng trên nhiều ổ đĩa cứng. Ngoài ra, nếu điều op muốn là tránh sao chép các tập tin, thì đó là một điều tốt cp -lkhông hoạt động trên các hệ thống tập tin.
lvella

2
Bạn có thể muốn sử dụng cp -a(từ đồng nghĩa với cp -RPp) để giữ tất cả các thuộc tính của tệp và tránh theo các liên kết tượng trưng sau: ở đây lệnh trở thành cp -al dir1/* dir2/* merge.
tricasse

5

Bạn có thể sử dụng đổi tên (còn gọi là prename, từ gói perl) cho điều đó. Coi chừng tên đó không nhất thiết phải nói đến lệnh tôi mô tả bên ngoài debian / ubfox (mặc dù đó là một tệp perl di động duy nhất nếu bạn cần).

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

Bạn cũng có tùy chọn sử dụng vidir (từ moreutils) và chỉnh sửa đường dẫn tệp từ trình soạn thảo văn bản ưa thích của bạn.


3

Tôi thích các giải pháp rsyncprename , nhưng nếu bạn thực sự muốn làm cho mv thực hiện công việc và

  • bạn tìm thấy biết -print0-depth,
  • xargs của bạn biết -0,
  • bạn đã printf ,

sau đó có thể xử lý một số lượng lớn các tệp có thể có khoảng trắng ngẫu nhiên trong tên của chúng, tất cả đều có tập lệnh shell kiểu Bourne:

#!/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

Bạn có thể yêu cầu xargs phân định đầu vào của nó thành dòng mới và bỏ qua bản dịch. ví dụ như sau đây sẽ tìm và xóa tất cả các tệp torrent của bạn trong thư mục hiện tại, ngay cả những tệp có ký tự unicode hoặc một số tomfoolery khác. find . -name '*.torrent' | xargs -d '\n' rm
PRS

2

Lực lượng vũ phu 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

kiểm tra làm điều này

# 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 đã chỉ định hàng triệu tệp, có khả năng phá vỡ cấu trúc này. Ngoài ra, nó sẽ không xử lý đúng tên tệp có dấu cách, dòng mới, v.v.
Chris Johnsen

0

Tôi đã phải làm điều này nhiều lần cho các cây mã nguồn ở các giai đoạn phát triển khác nhau. Giải pháp của tôi là sử dụng Git theo cách sau:

  1. Tạo một kho lưu trữ git và thêm tất cả các tệp từ dir1.
  2. Cam kết
  3. Xóa tất cả các tệp và sao chép trong các tệp từ dir2
  4. Cam kết
  5. Xem sự khác biệt giữa hai điểm cam kết và đưa ra quyết định cẩn thận về cách tôi muốn hợp nhất kết quả.

Bạn có thể tinh chỉnh nó với phân nhánh và vv nhưng đây là ý tưởng chung. Và bạn đã bớt lo sợ về việc nhét nó lên vì bạn đã hoàn thành ảnh chụp nhanh của từng trạng thái.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.