Sao chép thư mục đệ quy, ngoại trừ một số thư mục


197

Tôi đang cố gắng viết một tập lệnh bash đơn giản sẽ sao chép toàn bộ nội dung của một thư mục bao gồm các tệp và thư mục ẩn vào một thư mục khác, nhưng tôi muốn loại trừ một số thư mục cụ thể. Làm thế nào tôi có thể đạt được điều này?


1
Tôi tưởng tượng một cái gì đó như tìm thấy. -name * được chuyển sang grep / v "loại trừ mẫu" để lọc những cái bạn không muốn và sau đó chuyển sang cp để thực hiện sao chép.
i_am_jorf

1
Tôi đã cố gắng làm một cái gì đó như thế, nhưng không thể tìm ra cách sử dụng cp với một đường ống
trobrock

1
Điều này có lẽ nên đi đến siêu người dùng. Lệnh bạn đang tìm kiếm là xargs. Bạn cũng có thể làm một cái gì đó giống như hai tar được kết nối bằng một đường ống.
Kyle Mông

1
Có thể là muộn và nó không trả lời chính xác câu hỏi nhưng đây là một mẹo: Nếu bạn muốn loại trừ chỉ những đứa trẻ ngay lập tức của thư mục, bạn có thể tận dụng kết hợp mô hình bash, ví dụ:cp -R !(dir1|dir2) path/to/destination
Boris D. Teoharov

1
Lưu ý rằng !(dir1|dir2)mẫu yêu cầu extglobphải được bật ( shopt -s extglobđể bật nó).
Boris D. Teoharov

Câu trả lời:


334

Sử dụng rsync:

rsync -av --exclude='path1/to/exclude' --exclude='path2/to/exclude' source destination

Lưu ý rằng sử dụng sourcesource/là khác nhau. Dấu gạch chéo có nghĩa là sao chép nội dung của thư mục sourcevào destination. Không có dấu gạch chéo, nó có nghĩa là sao chép thư mục sourcevào destination.

Ngoài ra, nếu bạn có nhiều thư mục (hoặc tệp) để loại trừ, bạn có thể sử dụng --exclude-from=FILE, đâu FILElà tên của tệp chứa tệp hoặc thư mục để loại trừ.

--exclude cũng có thể chứa các ký tự đại diện, chẳng hạn như --exclude=*/.svn*


10
Tôi đề nghị thêm --dry-run để kiểm tra xem tập tin nào sẽ được sao chép.
loretoparisi

1
@AmokHuginnsson - Bạn đang sử dụng hệ thống nào? Rsync được bao gồm theo mặc định trong tất cả các bản phân phối Linux chính mà tôi biết, bao gồm cả RHEL, CentOS, Debian và Ubuntu và tôi tin rằng nó cũng có trong FreeBSD.
siliconrockstar

1
Đối với các bản phân phối có nguồn gốc của RHEL: yum cài đặt rsync hoặc trên các bản phát hành dựa trên Debian: apt-get install rsync. Trừ khi bạn xây dựng máy chủ của mình từ cơ sở tuyệt đối trên phần cứng của riêng bạn, đây không phải là vấn đề. rsync được cài đặt theo mặc định trên các hộp Amazon EC2 của tôi, cũng như các hộp của tôi từ ZeroLag và RackSpace.
siliconrockstar 2/2/2015

2
rsync dường như cực kỳ chậm so với cp? Ít nhất đây là kinh nghiệm của tôi.
Kojo

2
Ví dụ: bỏ qua thư mục git:rsync -av --exclude='.git/' ../old-repo/ .
nycynik

40

Sử dụng tar cùng với một đường ống.

cd /source_directory
tar cf - --exclude=dir_to_exclude . | (cd /destination && tar xvf - )

Bạn thậm chí có thể sử dụng kỹ thuật này trên ssh.


Cách tiếp cận này trước tiên không cần thiết sử dụng nguồn đích (và loại trừ các thư mục cụ thể trong kho lưu trữ) và sau đó gỡ bỏ nó tại mục tiêu. Không được khuyến khích!
Wonders

4
@Waldheri bạn sai rồi. Đây là giải pháp tốt nhất. Nó thực hiện chính xác những gì OP yêu cầu và nó hoạt động trên cài đặt mặc định của hầu hết các * nix như HĐH. Taring và uning được thực hiện một cách nhanh chóng mà không có hệ thống tập tin nào (trong bộ nhớ), chi phí của tar + unar này là không đáng kể.
AmokHuginnsson

@WouterDonders Tar là chi phí tối thiểu. Nó không áp dụng nén.
Kyle Mông

9

Bạn có thể sử dụng findvới -prunetùy chọn.

Một ví dụ từ man find:

       cd / nguồn-dir
       tìm thấy . -name .snapshot -prune -o \ (\! -name * ~ -print0 \) |
       cpio -pmd0 / mệnh-dir

       Lệnh này sao chép nội dung của / source-dir sang / Dest-dir, nhưng bỏ qua
       các tập tin và thư mục có tên .snapshot (và bất cứ thứ gì trong đó). Nó cũng
       bỏ qua các tập tin hoặc thư mục có tên kết thúc bằng ~, nhưng không phải con‐ của chúng
       Lều trại. Cấu trúc -prune -o \ (... -print0 \) khá phổ biến. Các
       ý tưởng ở đây là biểu thức trước -prune khớp với những thứ
       được cắt tỉa. Tuy nhiên, hành động -prune tự trả về đúng, vì vậy
       theo sau -o đảm bảo rằng phía bên tay phải chỉ được đánh giá cho
       những thư mục không được cắt tỉa (nội dung của việc cắt tỉa
       thư mục thậm chí không được truy cập, vì vậy nội dung của chúng không liên quan).
       Biểu thức ở phía bên phải của -o chỉ trong ngoặc đơn
       cho rõ ràng. Nó nhấn mạnh rằng hành động -print0 chỉ diễn ra
       cho những thứ không có - áp dụng cho chúng. Vì
       điều kiện `và 'mặc định giữa các kiểm tra liên kết chặt chẽ hơn -o, điều này
       dù sao cũng là mặc định, nhưng dấu ngoặc đơn giúp hiển thị những gì đang diễn ra
       trên.

Đạo cụ để định vị một ví dụ có liên quan cao trực tiếp từ một trang.
David M

Trông thật đấy! Điều này cũng có sẵn trong các tài liệu trực tuyến . Thật không may, cpiochưa được đóng gói cho MSYS2.
underscore_d

3

bạn có thể sử dụng tar, với tùy chọn --exclude, và sau đó gỡ bỏ nó ở đích. ví dụ

cd /source_directory
tar cvf test.tar --exclude=dir_to_exclude *
mv test.tar /destination 
cd /destination  
tar xvf test.tar

xem trang người đàn ông của tar để biết thêm


2

Tương tự như ý tưởng của Jeff (chưa được kiểm tra):

find . -name * -print0 | grep -v "exclude" | xargs -0 -I {} cp -a {} destination/

Xin lỗi, nhưng tôi thực sự không hiểu tại sao 5 người lại ủng hộ điều này khi nó được thừa nhận chưa được kiểm tra và dường như không hoạt động trong một thử nghiệm đơn giản: Tôi đã thử điều này trong một thư mục con /usr/share/iconsvà ngay lập tức có find: paths must precede expression: 22x22một trong những tiểu thư ở đó . Lệnh của tôi là find . -name * -print0 | grep -v "scalable" | xargs -0 -I {} cp -a {} /z/test/(thừa nhận, tôi đang dùng MSYS2, vì vậy thực sự là vậy /mingw64/share/icons/Adwaita, nhưng tôi không thể thấy đây là lỗi của MSYS2 như thế nào)
underscore_d

0
EXCLUDE="foo bar blah jah"                                                                             
DEST=$1

for i in *
do
    for x in $EXCLUDE
    do  
        if [ $x != $i ]; then
            cp -a $i $DEST
        fi  
    done
done

Chưa được kiểm tra ...


Điều này là không đúng. Một vài vấn đề: Như đã viết, nó sẽ sao chép một tệp không được loại trừ nhiều lần (số mục cần loại trừ trong trường hợp này là 4). Ngay cả khi bạn cố gắng sao chép 'foo', mục đầu tiên trong danh sách loại trừ, nó vẫn sẽ được sao chép khi bạn truy cập vào x = bar và tôi vẫn là foo. Nếu bạn khăng khăng làm điều này mà không có các công cụ có sẵn (ví dụ rsync), hãy di chuyển bản sao sang câu lệnh if bên ngoài vòng lặp 'for x in ...' và làm cho vòng lặp 'for x ...' thay đổi câu lệnh logic trong các tập tin sao chép if (true). Điều này sẽ ngăn bạn sao chép nhiều lần.
Eric Bringley

0

lấy cảm hứng từ câu trả lời của @ SteveLazaridis, sẽ thất bại, đây là hàm shell POSIX - chỉ cần sao chép và dán vào một tệp có tên cpxtrong yout $PATHvà làm cho nó có thể thực thi được ( chmod a+x cpr). [Nguồn hiện được duy trì trong GitLab của tôi .

#!/bin/sh

# usage: cpx [-n|--dry-run] "from_path" "to_path" "newline_separated_exclude_list"
# limitations: only excludes from "from_path", not it's subdirectories

cpx() {
# run in subshell to avoid collisions
  (_CopyWithExclude "$@")
}

_CopyWithExclude() {
  case "$1" in
    -n|--dry-run) { DryRun='echo'; shift; } ;;
  esac

  from="$1"
  to="$2"
  exclude="$3"

  $DryRun mkdir -p "$to"

  if [ -z "$exclude" ]; then
      cp "$from" "$to"
      return
  fi

  ls -A1 "$from" \
    | while IFS= read -r f; do
        unset excluded
        if [ -n "$exclude" ]; then
          for x in $(printf "$exclude"); do
          if [ "$f" = "$x" ]; then
              excluded=1
              break
          fi
          done
        fi
        f="${f#$from/}"
        if [ -z "$excluded" ]; then
          $DryRun cp -R "$f" "$to"
        else
          [ -n "$DryRun" ] && echo "skip '$f'"
        fi
      done
}

# Do not execute if being sourced
[ "${0#*cpx}" != "$0" ] && cpx "$@"

Ví dụ sử dụng

EXCLUDE="
.git
my_secret_stuff
"
cpr "$HOME/my_stuff" "/media/usb" "$EXCLUDE"

Có vẻ không ích gì khi nói rằng câu trả lời của ai đó "sẽ thất bại" mà không giải thích điều gì sai với nó và cách bạn khắc phục điều đó ...
underscore_d

@underscore_d: đúng, trong nhận thức muộn, đặc biệt là bây giờ tôi không thể nhớ những gì đã thất bại :-(
go2null

Nhiều thứ: (1) nó sao chép các tệp nhiều lần và (2) logic vẫn sao chép các tệp cần loại trừ. Chạy qua các vòng bằng i = foo: nó sẽ được sao chép 3 lần thay vì 4 cho bất kỳ tệp nào khác, ví dụ i = test.txt.
Eric Bringley

1
cảm ơn @EricBringley vì đã làm rõ những thiếu sót trong câu trả lời của Steve. (Anh ấy đã nói rằng nó chưa được kiểm tra .)
go2null
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.