Sáp nhập các thư mục với mv?


149

Nếu tôi sử dụng mvđể di chuyển một thư mục gọi là "thư mục" sang một thư mục đã chứa "thư mục" thì chúng sẽ hợp nhất hay nó sẽ được thay thế?

Câu trả lời:


120

mvkhông thể hợp nhất hoặc ghi đè các thư mục, nó sẽ thất bại với thông báo "mv: không thể di chuyển 'a' sang 'b': Thư mục không trống" , ngay cả khi bạn đang sử dụng --forcetùy chọn.


Bạn có thể làm việc xung quanh này sử dụng các công cụ khác (như rsync, findhoặc thậm chí cp), nhưng bạn cần phải xem xét một cách cẩn thận những tác động:

  • rsynccó thể hợp nhất các nội dung của một thư mục vào một (lý tưởng với --remove-source-files1 tùy chọn để xóa một cách an toàn chỉ những tập tin nguồn mà đã được chuyển giao thành công, và với tùy chọn thông thường cho phép / quyền sở hữu / thời gian bảo quản -anếu bạn muốn)
    ... nhưng đây là một hoạt động sao chép đầy đủ và do đó có thể rất nhiều đĩa.
  • Tùy chọn hiện ưa thích: Bạn có thể kết hợp rsync's --link-dest=DIRtùy chọn (để tạo liên kết cứng thay vì sao chép nội dung tập tin, nếu có thể) và --remove-source-filesđể có được một ngữ nghĩa rất giống với một thường xuyên mv.
    Đối với điều này, --link-destcần phải được cung cấp một đường dẫn tuyệt đối đến thư mục nguồn (hoặc một đường dẫn tương đối từ đích đến nguồn ).
    ... nhưng đây là sử dụng --link-destmột cách ngoài ý muốn (mà có thể hoặc không có thể gây ra các biến chứng), đòi hỏi phải biết (hoặc xác định) đường dẫn tuyệt đối đến nguồn (như một đối số --link-dest), và một lần nữa để lại một cấu trúc thư mục trống được làm sạch lên như mỗi 1 .
  • Bạn có thể sử dụngfind để liên tục tái cấu trúc thư mục nguồn vào mục tiêu, sau đó cá nhân di chuyển các tập tin thực tế
    ... nhưng điều này đã recurse thông qua các nguồn nhiều lần và có thể gặp phải điều kiện chủng tộc (thư mục mới được tạo ra tại nguồn trong quá trình nhiều bước )
  • cpcó thể tạo liên kết cứng (chỉ cần đặt, gợi ý bổ sung cho cùng một tập tin hiện có), mà tạo ra một kết quả rất giống với một sáp nhập mv(và rất IO-hiệu quả vì chỉ có con trỏ được tạo ra và không có dữ liệu thực tế phải được sao chép)
    ... nhưng này một lần nữa phải chịu một điều kiện cuộc đua có thể (các tệp mới tại nguồn bị xóa ngay cả khi chúng không được sao chép ở bước trước)

Những cách giải quyết nào (nếu có) là phù hợp sẽ phụ thuộc rất nhiều vào trường hợp sử dụng cụ thể của bạn.
Như mọi khi, hãy suy nghĩ trước khi bạn thực hiện bất kỳ lệnh nào trong số này và có bản sao lưu.


1: Lưu ý rằng rsync --remove-source-filessẽ không xóa bất kỳ thư mục nào, vì vậy bạn sẽ phải làm một cái gì đó như find -depth -type d -empty -deletesau đó để thoát khỏi cây thư mục nguồn trống.


Có vẻ như bạn đã thử chỉ một lần thực hiện mv. Câu trả lời này sẽ tốt hơn với một sự thật rộng lớn hơn. Linux, BSD và Unix "thực" hoặc tài liệu tham khảo từ POSIX hoặc SUS.
Warren Young

@WarrenYoung Bạn nói đúng, tôi chỉ thử mvtriển khai được sử dụng bởi Debian - sự nhấn mạnh đang được thử , vì trang này không đề cập đến hành vi này ...
n.st

38
Nhược điểm của rsync là nó thực sự sao chép dữ liệu, thay vì chỉ thay đổi liên kết cứng, có khả năng tốn nhiều tài nguyên nếu bạn đang xử lý nhiều dữ liệu.
Jonathan Mayer

7
@Keith Lưu ý rằng --deletechỉ xóa các tệp trong thư mục đích không tồn tại trong thư mục nguồn.
n.st

1
@JonathanMayer rsync như một số tính năng liên quan đến liên kết cứng. Ví dụ: bạn chỉ có thể bảo vệ các liên kết cứng với -Hchức năng hoặc bạn có thể liên kết các tệp cứng ở đích bằng cách sử dụng --link-dest. Xem trang người đàn ông trước khi sử dụng chúng, mặc dù.
allo

87
rsync -av /source/ /destination/
(after checking)
rm -rf /source/

Điều này sẽ loại bỏ các tệp nguồn như trong bình luận của n.st?
Dominique

3
Không, tôi muốn làm theo hai bước vì lý do an toàn. Sáp nhập và loại bỏ nguồn là không thể đảo ngược. Bước bổ sung trong n.st anwer cũng cần thiết (để loại bỏ các thư mục).
fazie

3
--remove-source-filescó lợi thế là chỉ xóa các tệp đã được chuyển thành công, vì vậy bạn có thể sử dụng findđể xóa các thư mục trống và sẽ được để lại mọi thứ không được chuyển mà không phải kiểm tra rsyncđầu ra.
n.st

4
Nhưng nó không thực sự di chuyển - tác động tốc độ là rất lớn, nếu các tập tin lớn có liên quan.
Alex

Nhưng bạn không thể thực hiện di chuyển thuần túy và sáp nhập thực sự.
fazie

64

Bạn có thể sử dụng -ltùy chọn của lệnh cp , tạo ra các liên kết cứng của các tệp trên cùng một hệ thống tệp thay vì các bản sao dữ liệu đầy đủ. Lệnh sau sao chép thư mục source/foldervào thư mục mẹ ( destination) đã chứa thư mục có tên folder.

cp -rl source/folder destination
rm -r source/folder

Bạn cũng có thể muốn sử dụng -P( --no-dereference- không hủy liên kết tượng trưng) hoặc -a( --archive- bảo toàn tất cả siêu dữ liệu, cũng bao gồm -Ptùy chọn), tùy thuộc vào nhu cầu của bạn.


7
@rautamiekka: Tôi giả sử bạn đang hỏi lý do sử dụng các liên kết cứng. Nếu bạn không biết liên kết cứng là gì và tại sao bạn nên sử dụng chúng, thì có lẽ bạn không nên đi theo con đường này. Tuy nhiên, việc tạo các liên kết cứng không thực hiện một bản sao đầy đủ, do đó thao tác này sẽ nhận được các đơn đặt hàng có độ lớn ít thời gian hơn so với bản sao đầy đủ. Và, bạn sẽ sử dụng các liên kết cứng thay vì liên kết mềm để bạn có thể xóa các tệp nguồn và vẫn có dữ liệu chính xác thay vì con trỏ tới các đường dẫn không hợp lệ. Và cpthay rsyncvì kể từ khi mọi hệ thống có cpvà mọi người đều quen thuộc với nó.
palswim

7
Sự sáng chói của giải pháp này có thể được xem xét vì không phải là câu trả lời được chấp nhận. Đó là một giải pháp thanh lịch. Bạn có được khả năng hợp nhất cpvới thời gian hoạt động của mv.
TheHerk

2
nếu bạn biết rằng bạn không cần phải di chuyển các tệp đã tồn tại ở đích bạn cũng muốn thêm-n
ndemou

1
@Ruslan: Điều này đúng, nhưng bạn không thể di chuyển mà không có bản sao trên các hệ thống tập tin với bất kỳ phương pháp nào. Thậm chí mv /fs1/file /fs2/(trên các hệ thống tập tin) sẽ thực hiện một bản sao và sau đó xóa.
palswim

2
Đúng, nhưng trong khi mvsẽ hoạt động (miễn là thư mục tiêu chưa tồn tại) ngay cả khi không "hiệu quả" hoặc bất cứ điều gì bạn gọi nó, cp -rlsẽ thất bại.
Ruslan

23

Tôi muốn giới thiệu bốn bước sau:

cd ${SOURCE}; 
find . -type d -exec mkdir -p ${DEST}/\{} \; 
find . -type f -exec mv \{} ${DEST}/\{} \; 
find . -type d -empty -delete

hoặc tốt hơn nữa, đây là một kịch bản thực hiện ngữ nghĩa tương tự như mv:

#!/bin/bash

DEST="${@:${#@}}"
ABS_DEST="$(cd "$(dirname "$DEST")"; pwd)/$(basename "$DEST")"

for SRC in ${@:1:$((${#@} -1))}; do   (
    cd "$SRC";
    find . -type d -exec mkdir -p "${ABS_DEST}"/\{} \;
    find . -type f -exec mv \{} "${ABS_DEST}"/\{} \;
    find . -type d -empty -delete
) done

Đối số là NGUỒN, DEST
giả

Điều này có vẻ khá hữu ích. Tôi muốn sử dụng nó để dọn dẹp ổ cứng của tôi. Bất kỳ chuyên gia nào khác có thể nhận xét về nó trước khi tôi giao một loạt các bản sao lưu cho kịch bản này? :-)
LarsH

BTW, nếu bạn muốn thực hiện tương đương rsync -u(chỉ cập nhật nếu mới hơn), mv(ít nhất là trong một số phiên bản) cũng có thể thực hiện -utùy chọn này. Tuy nhiên, trong trường hợp đó, bạn có thể muốn xóa các thư mục nguồn không trống cũng như các thư mục trống, để bao quát các trường hợp trong đó các tệp trong cây nguồn không mới hơn. @schuess: Có vẻ như có thể có nhiều đối số SOURCE, nếu bạn cần điều đó.
LarsH

1
Điều này không xử lý tốt khoảng trắng. Tôi đã thử nó với một số thư mục có khoảng trắng trong đó và kết thúc với một loạt các thư mục lồng nhau vô hạn.
rofer

16

Đây là một cách sẽ hợp nhất các thư mục. Nó nhanh hơn nhiều so với rsync vì nó chỉ đổi tên các tệp thay vì sao chép chúng và sau đó xóa chúng.

cd source; find -type f -print0 | xargs -0 -n 1 -I {} mv '{}' 'dest/{}'

Điều đó thú vị nhưng chỉ liên quan một cách mơ hồ đến chủ đề và thậm chí không từ xa những gì người dùng hỏi về.
Shadur

17
Trên thực tế, mã của Jewel thực hiện chính xác những gì người dùng yêu cầu, ngoại trừ việc tạo các thư mục bị thiếu. Có lẽ bạn nên nhìn lại?
Jonathan Mayer

3
Tôi sẽ thêm để sử dụng "-print0" trong find và "-0" trong xargs vì có những tệp có khoảng trắng trong tên. Ngoài ra, có một vấn đề nhỏ, nếu một tên chứa dấu ngoặc đơn thì chúng sẽ không được di chuyển.
markuz

2
Tốc độ này nhanh hơn nhiều so với rsync đối với một số lượng nhỏ tệp, nhưng nó tạo ra một quy trình mới cho mọi tệp, do đó hiệu suất rất tệ với một số lượng lớn tệp nhỏ. Câu trả lời của @ palswim không gặp phải vấn đề này.
b0fh

2
Lệnh sẽ thất bại, nếu trong destđã là một thư mục có cùng tên như trong source. Và các tập tin sẽ được chuyển đến một dest, trong đó source. Lệnh không làm gì hơnmv source/* source/dest/.
ceving

3

Một cách để thực hiện điều này sẽ là sử dụng:

mv folder/* directory/folder/
rmdir folder

Miễn là không có hai tệp nào có cùng tên folderdirectory/folderbạn sẽ đạt được cùng một kết quả, tức là hợp nhất.


3
Làm thế nào chính xác rm folderlàm việc?
JakeGould

5
@JakeGould Không hề. :)
n.st

rm folder -fRluôn làm việc cho tôi
Bạch tuộc

2
Xin lưu ý rằng điều này sẽ không hoạt động đối với các tệp bị ẩn
b0fh

2

Đối với các bản sao thuần túy nhất, tôi sử dụng phương pháp sao chép khối B (-) B.

ví dụ, từ bên trong đường dẫn nguồn ('cd' nếu cần):

tar cBf - <sourcefolder> | (cd /your/target/folder ; tar xBf -)

điều này tạo ra một bản sao chính xác của cây nguồn, VỚI chủ sở hữu và các quyền còn nguyên vẹn. Và nếu thư mục đích tồn tại, dữ liệu sẽ được hợp nhất. Chỉ các tệp đã tồn tại sẽ được ghi đè.

Thí dụ:

 $ cd /data1/home
 $ tar cBf - jdoe | (cd /data2/home ; tar xBf -)

Khi hành động sao chép thành công, bạn có thể xóa nguồn ( rm -rf <source>). Tất nhiên đây không phải là một động thái chính xác: dữ liệu sẽ được sao chép, cho đến khi bạn xóa nguồn.

Như tùy chọn, bạn có thể dài dòng (hiển thị trên màn hình tệp đang được sao chép), với -v: tar cBvf -

  • c: tạo nên
  • B: đọc toàn bộ khối (đối với đọc ống)
  • v: dài dòng
  • f: tập tin để viết
  • x: trích xuất
  • -: stdout / stdin

sourcefoldercũng có thể là *(cho bất cứ điều gì trong thư mục hiện tại)


Việc chỉ định f -tar thường không cần thiết - mặc định là đọc từ stdin / write sang stdout.
muru

1

Đây là một kịch bản làm việc cho tôi. Tôi thích mv hơn rsync, vì vậy tôi sử dụng các giải pháp của Jewel và Jonathan Mayer.

#!/bin/bash

# usage source1 .. sourceN dest

length=$(($#-1))
sources=${@:1:$length}
DEST=$(readlink -f ${!#})
for SRC in $sources; do
    pushd $SRC;
    find . -type d -exec mkdir -p ${DEST}/{} \;
    find . -type f -exec mv {} ${DEST}/{} \;
    find . -type d -empty -delete
    popd
done

Giải pháp này không thoát đúng tên đường dẫn, hãy cẩn thận.
dùng12439

@ user12439, tôi sẽ cập nhật giải pháp nếu bạn chỉ cho tôi phần nào cần sửa.
xer0x

1

Nó không phải là một ý tưởng tốt để sử dụng các lệnh như cp hoặc rsync. Đối với các tệp lớn, sẽ mất nhiều thời gian. mv nhanh hơn nhiều vì nó chỉ cập nhật các nút mà không sao chép các tệp vật lý. Một lựa chọn tốt hơn là sử dụng trình quản lý tệp của hệ điều hành của bạn. Đối với Opensuse, có một trình quản lý tệp có tên Konquerer. Nó có thể di chuyển các tập tin mà không thực sự sao chép chúng. Nó có chức năng "cắt và dán" như trong Windows. Chỉ cần chọn tất cả các thư mục con trong thư mục A. Nhấp chuột phải và "di chuyển vào" thư mục B có thể chứa các thư mục con có cùng tên. Nó sẽ hợp nhất chúng. Ngoài ra còn có các tùy chọn cho dù bạn muốn ghi đè hoặc đổi tên các tệp có cùng tên.


1
OP hỏi điều gì xảy ra khi mvđược sử dụng.
don_crissti

0

Giải pháp Python

Vì tôi không thể tìm thấy một giải pháp có sẵn thỏa đáng, tôi quyết định tạo một kịch bản Python nhanh để đạt được nó.

Đặc biệt, phương pháp này hiệu quả vì nó chỉ đi theo cây tệp nguồn một lần từ dưới lên.

Nó cũng sẽ cho phép bạn nhanh chóng điều chỉnh những thứ như xử lý ghi đè tập tin theo ý thích của bạn.

Sử dụng:

move-merge-dirs src/ dest/

sẽ di chuyển tất cả nội dung src/*vào dest/src/sẽ biến mất.

di chuyển hợp nhất-dirs

#!/usr/bin/env python3

import argparse
import os

def move_merge_dirs(source_root, dest_root):
    for path, dirs, files in os.walk(source_root, topdown=False):
        dest_dir = os.path.join(
            dest_root,
            os.path.relpath(path, source_root)
        )
        if not os.path.exists(dest_dir):
            os.makedirs(dest_dir)
        for filename in files:
            os.rename(
                os.path.join(path, filename),
                os.path.join(dest_dir, filename)
            )
        for dirname in dirs:
            os.rmdir(os.path.join(path, dirname))
    os.rmdir(source_root)

if __name__ == '__main__':
    parser = argparse.ArgumentParser(
        description='Move merge src/* into dest. Overwrite existing files.'
    )
    parser.add_argument('src_dir')
    parser.add_argument('dest_dir')
    args = parser.parse_args()
    move_merge_dirs(args.src_dir, args.dest_dir)

GitHub ngược dòng .

Xem thêm: https://stackoverflow.com/questions/22588225/how-do-you-merge-two-directories-or-move-with-replace-from-the-windows-command


0

Đây là lệnh để di chuyển tệp và thư mục đến đích khác:

$ mv /source/path/folder /target/destination/

Ghi : mvlệnh sẽ không hoạt động nếu thư mục được được sáp nhập (tức là một thư mục khác có cùng tên đã tồn tại trong đích) và một nơi lý là không có sản phẩm nào .

mv: không thể di chuyển '/ source / path / thư mục' sang '/ target / Destination / thư mục': Thư mục không trống

Nếu thư mục đích trống, lệnh trên sẽ hoạt động tốt.

Vì vậy, để hợp nhất cả hai thư mục trong mọi trường hợp,
Hoặc thực hiện theo 2 lệnh:

$ cp -rf /source/path/folder /target/destination/
$ rm -rf /source/path/folder

Hoặc kết hợp cả hai như một lệnh một lần:

$ cp -rf /source/path/folder /target/destination/ && rm -rf /source/path/folder

mv = di chuyển
cp = sao chép
rm = xóa

-r cho thư mục (thư mục)
-f thực thi lực lượng

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.