Làm phẳng một thư mục lồng nhau


71

Điều này có lẽ rất đơn giản, nhưng tôi không thể tìm ra nó. Tôi có một cấu trúc thư mục như thế này (dir2 nằm trong dir1):

/dir1
    /dir2 
       |
        --- file1
       |
        --- file2

Cách tốt nhất để 'làm phẳng' cấu trúc đạo diễn này theo cách như vậy để có được file1 và file2 trong dir1 chứ không phải dir2.


Câu trả lời:


75

Bạn có thể làm điều này với GNU findvà GNU mv:

find /dir1 -mindepth 2 -type f -exec mv -t /dir1 -i '{}' +

Về cơ bản, cách thức hoạt động nếu findđi qua toàn bộ cây thư mục và với mỗi tệp ( -type f) không có trong thư mục cấp cao nhất ( -mindepth 2), nó chạy một mvđể di chuyển nó đến thư mục bạn muốn ( -exec mv … +). Đối -tsố để mvcho phép bạn chỉ định thư mục đích trước tiên, điều này là cần thiết bởi vì +hình thức -execđặt tất cả các vị trí nguồn ở cuối lệnh. Các -ilàm cho mvhỏi trước khi ghi đè lên bất kỳ bản sao; bạn có thể thay thế -fđể ghi đè chúng mà không cần hỏi (hoặc -nkhông hỏi hoặc ghi đè).

Như Stephane Chazelas chỉ ra, phần trên chỉ hoạt động với các công cụ GNU (vốn là tiêu chuẩn trên Linux, nhưng không phải hầu hết các hệ thống khác). Sau đây là hơi chậm (vì nó gọi mvnhiều lần) nhưng phổ quát hơn nhiều:

find /dir1 -mindepth 2 -type f -exec mv -i '{}' /dir1 ';'

3
Được chỉnh sửa để sử dụng -exec +để nó không thực hiện một số lượng lớn các quy trình củamv
Random832

1
@ Random832 Và sẽ hoàn nguyên lại, vì + không hoạt động. mvcần đích là đối số cuối cùng, nhưng + sẽ có các nguồn làm đối số cuối cùng. Tìm thậm chí không chấp nhận cú pháp bạn đã thay đổi thành ( find: missing argument to `-exec')
derobert

1
@ Random832 nhưng tôi giả sử mvcó một -tchúng ta có thể sử dụng, vì vậy tôi sẽ thay đổi nó để đó.
derobert

1
@Dom findin các tập tin ẩn (dấu chấm) theo mặc định. Độ sâu liên quan đến thư mục bạn vượt qua để tìm.
derobert

1
Hoặc find ./dir -mindepth 2 -type f -exec mv -f '{}' ./dir ';'nếu ghi đè các bản sao
Atav32

33

Trong zsh:

mv dir1/*/**/*(.D) dir1

**/đi qua các thư mục con đệ quy. Vòng loại toàn cầu chỉ . khớp với các tệp thông thường và Dđảm bảo rằng các tệp chấm được bao gồm (theo mặc định, các tệp có tên bắt đầu bằng một .loại trừ các ký tự đại diện). Để dọn sạch các thư mục trống bây giờ sau đó, hãy chạy rmdir dir1/**/*(/Dod)- /giới hạn các thư mục và odđặt hàng độ sâu khớp trước để xóa dir1/dir2/dir3trước dir1/dir2.

Nếu tổng chiều dài của tên tệp rất lớn, bạn có thể gặp phải giới hạn về độ dài dòng lệnh. Zsh đã tích hợp sẵn mvrmdirkhông bị ảnh hưởng bởi giới hạn này: chạy zmodload zsh/filesđể kích hoạt chúng.

Chỉ với các công cụ POSIX:

find dir1 -type f -exec mv {} dir1 \;
find dir1 -depth -exec rmdir {} \;

hoặc (nhanh hơn vì không phải chạy một quy trình riêng cho mỗi tệp)

find dir1 -type f -exec sh -c 'mv "$@" dir1' _ {} +
find dir1 -depth -exec rmdir {} +

1
Đây phải là câu trả lời được chấp nhận! Đặc biệt với phiên bản zsh súc tích.
Adamski

3

Hãy thử làm điều này:

cp /dir1/dir2/file{1,2} /another/place

hoặc cho mỗi tệp khớp file[0-9]*trong thư mục con:

cp /dir1/dir2/file[0-9]* /another/place

Xem http://mywiki.wooledge.org/glob


Tôi nên đã chỉ ra điều này, nhưng tôi phải sử dụng nhiều tệp {}trong vấn đề thực sự của mình.
rùa

Xem giải pháp thứ hai của tôi
Gilles Quenot

Chơi lô tô. Cảm ơn đã giúp đỡ. Đây chắc chắn là giải pháp tốt nhất.
rùa

2

Tôi đã viết hai hàm bạn có thể sử dụng cùng nhau để làm điều đó, bạn có thể giới hạn cấp độ thư mục bằng cách thêm một -maxdepth $VALtham số.

# This scripts flattens the file directory
# Run this script with a folder as parameter:
# $ path/to/script path/to/folder

#!/bin/bash

rmEmptyDirs(){
    local DIR="$1"
    for dir in "$DIR"/*/
    do
        [ -d "${dir}" ] || continue # if not a directory, skip
        dir=${dir%*/}
        if [ "$(ls -A "$dir")" ]; then
            rmEmptyDirs "$dir"
        else
            rmdir "$dir"
        fi
    done
    if [ "$(ls -A "$DIR")" ]; then
        rmEmptyDirs "$DIR"
    fi
}

flattenDir(){
    local DIR="$1"
    find "$DIR" -mindepth 2 -type f -exec mv -i '{}' "$DIR" ';'
}

read -p "Do you wish to flatten folder: ${1}? " -n 1 -r
echo    # (optional) move to a new line
if [[ $REPLY =~ ^[Yy]$ ]]
then
    flattenDir "$1" &
    rmEmptyDirs "$1" &
    echo "Done";
fi

Man, tôi chỉ sử dụng sai kịch bản của bạn bằng cách quên đối số đường dẫn, điều đó thực sự làm hỏng máy chủ của tôi. Ok, tôi là người sao chép các thứ dán và lạm dụng chúng, nhưng các bạn, hãy khôn ngoan và thêm kiểm tra / xác nhận vào các tập lệnh xóa / di chuyển các thứ như thế ...
dulgan

Rất tiếc! Tôi rất tiếc khi nghe điều này. Hy vọng bạn có một bản sao lưu ... Tôi đã thêm một xác nhận để bảo vệ trong tương lai.
Bruno

Cảm ơn @Bruno theo cách này tốt hơn nhiều. Máy chủ của tôi vẫn đang chạy hoàn hảo, tôi đã nhận xét phần "làm phẳng" để chỉ xóa các thư mục trống từ gốc (và đó là lỗi của tôi), cho đến khi tôi thấy một lỗi khiến tôi ngừng chạy tập lệnh.
dulgan

1

Mở rộng câu trả lời phổ biến cho câu hỏi này, vì tôi có một trường hợp sử dụng để làm phẳng một thư mục chứa các tệp cùng tên.

dir1/
├── dir2
   └── file
└── dir3
    └── file

Trong trường hợp này, tùy chọn -i( --interactive) được truyền vào mvsẽ không mang lại kết quả mong muốn để làm phẳng cấu trúc thư mục và xử lý xung đột tên. Vì vậy, nó chỉ đơn giản được thay thế bằng --backup=t(tương đương --backup=numbered). Thêm tài liệu về tùy chọn -b( --backup) có sẵn tại https://www.gnu.org/software/coreutils/manual/coreutils.html#Backup-options .

Kết quả là:

find dir1/ -mindepth 2 -type f -exec mv -t dir1/ --backup=t '{}' +

Sản lượng nào:

dir1/
├── dir2
├── dir3
├── file
└── file.~1~

1

Cả tar và zip đều có khả năng kết hợp và sau đó loại bỏ cấu trúc thư mục, vì vậy tôi có thể nhanh chóng làm phẳng một thư mục lồng nhau với

tar -cvf all.tar *

tiếp theo là di chuyển all.tar đến một vị trí mới sau đó

tar -xvf all.tar --strip=4

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.