Salin folder secara rekursif, tidak termasuk beberapa folder

197

Saya mencoba menulis skrip bash sederhana yang akan menyalin seluruh isi folder termasuk file dan folder tersembunyi ke folder lain, tetapi saya ingin mengecualikan folder tertentu. Bagaimana saya bisa mencapai ini?

trobrock
sumber
1
Saya membayangkan sesuatu seperti menemukan. -nama * disalurkan ke grep / v "exclude-pattern" untuk memfilter yang tidak Anda inginkan dan kemudian disalurkan ke cp untuk melakukan penyalinan.
i_am_jorf
1
Saya mencoba melakukan sesuatu seperti itu, tetapi tidak bisa menemukan cara menggunakan cp dengan pipa
trobrock
1
Ini mungkin harus pergi ke pengguna super. Perintah yang Anda cari adalah xargs. Anda juga bisa melakukan sesuatu seperti dua tar yang dihubungkan oleh pipa.
Kyle Butt
1
Mungkin sudah terlambat dan tidak menjawab pertanyaan dengan akurat, tetapi inilah tipnya: Jika Anda ingin mengecualikan hanya anak-anak langsung dari direktori, Anda dapat memanfaatkan pencocokan pola bash, misalnyacp -R !(dir1|dir2) path/to/destination
Boris D. Teoharov
1
Perhatikan bahwa !(dir1|dir2)polanya extglobharus dinyalakan ( shopt -s extglobuntuk mengaktifkannya).
Boris D. Teoharov

Jawaban:

334

Gunakan rsync:

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

Perhatikan bahwa menggunakan sourcedan source/berbeda. Garis miring berarti menyalin isi folder sourceke dalamnya destination. Tanpa trailing slash, itu berarti menyalin folder sourceke dalamnya destination.

Atau, jika Anda memiliki banyak direktori (atau file) untuk dikecualikan, Anda dapat menggunakan --exclude-from=FILE, di mana FILEnama file yang berisi file atau direktori untuk dikecualikan.

--exclude mungkin juga berisi wildcard, seperti --exclude=*/.svn*

Kaleb Pederson
sumber
10
Saya sarankan untuk menambahkan --dry-run untuk memeriksa file mana yang akan disalin.
loretoparisi
1
@AmokHuginnsson - Sistem apa yang Anda gunakan? Rsync disertakan secara default di semua distro Linux arus utama yang saya ketahui, termasuk RHEL, CentOS, Debian, dan Ubuntu, dan saya percaya itu ada di FreeBSD juga.
siliconrockstar
1
Untuk distro yang diturunkan RHEL: yum install rsync, atau pada rilis berbasis Debian: apt-get install rsync. Kecuali jika Anda membangun server Anda dari basis absolut pada perangkat keras Anda sendiri, ini bukan masalah. rsync diinstal secara default di kotak Amazon EC2 saya, juga dan kotak saya dari ZeroLag dan RackSpace.
siliconrockstar
2
rsync tampaknya sangat lambat dibandingkan dengan cp? Setidaknya ini pengalaman saya.
Kojo
2
Misalnya untuk mengabaikan dir git:rsync -av --exclude='.git/' ../old-repo/ .
nycynik
40

Gunakan tar bersama dengan pipa.

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

Anda bahkan dapat menggunakan teknik ini di ssh.

Kyle Butt
sumber
Pendekatan ini pertama-tama tidak perlu menguji sumber target (dan mengecualikan direktori tertentu dalam arsip) dan kemudian menguraikannya pada target. Tidak direkomendasikan!
Wouter Donders
4
@Waldheri kamu salah. Ini solusi terbaik. Itu tidak persis apa yang diminta OP dan bekerja pada instalasi default sebagian besar * nix seperti OS. Taring dan untaring dilakukan dengan cepat tanpa artefak sistem file (dalam memori), biaya tar + untar ini dapat diabaikan.
AmokHuginnsson
@WouterDonders Tar adalah overhead minimal. Itu tidak menerapkan kompresi.
Kyle Butt
9

Anda dapat menggunakan finddengan -prunepilihan.

Contoh dari man find:

       cd / source-dir
       Temukan . -name .snapshot -prune -o \ (\! -name * ~ -print0 \) |
       cpio -pmd0 / dest-dir

       Perintah ini menyalin isi / source-dir ke / dest-dir, tetapi mengabaikan
       file dan direktori bernama .snapshot (dan apa pun di dalamnya). Juga
       menghilangkan file atau direktori yang namanya berakhir ~, tetapi tidak
       tenda. Konstruk -prune -o \ (... -print0 \) cukup umum. Itu
       ide di sini adalah bahwa ekspresi sebelum-cocok cocok dengan hal-hal yang
       untuk dipangkas. Namun, aksi -prune itu sendiri mengembalikan true, jadi
       mengikuti -o memastikan bahwa sisi kanan dievaluasi hanya untuk
       direktori-direktori yang tidak dipangkas (isi dari pemangkasan)
       direktori bahkan tidak dikunjungi, jadi isinya tidak relevan).
       Ekspresi di sisi kanan -o hanya dalam tanda kurung
       untuk kejelasan. Ini menekankan bahwa tindakan -print0 hanya terjadi
       untuk hal-hal yang tidak memiliki -prune diterapkan padanya. Karena
       kondisi `dan 'default di antara tes mengikat lebih erat dari -o, ini
       memang default, tetapi tanda kurung membantu menunjukkan apa yang sedang terjadi
       di.
Dijeda sampai pemberitahuan lebih lanjut.
sumber
Alat peraga untuk menemukan contoh yang sangat relevan langsung dari halaman manual.
David M
Terlihat bagus memang! Ini juga tersedia di dokumen online . Sayangnya cpiobelum dikemas untuk MSYS2.
underscore_d
3

Anda dapat menggunakan tar, dengan opsi --exclude, dan kemudian membukanya di tujuan. misalnya

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

lihat halaman manual tar untuk info lebih lanjut

ghostdog74
sumber
2

Mirip dengan ide Jeff (belum diuji):

find . -name * -print0 | grep -v "exclude" | xargs -0 -I {} cp -a {} destination/
Matthew Flaschen
sumber
Maaf, tapi saya benar-benar tidak mengerti mengapa 5 orang memilih ini ketika tidak diakui dan tampaknya tidak bekerja pada tes sederhana: Saya mencoba ini di subdir dari /usr/share/iconsdan segera sampai di find: paths must precede expression: 22x22mana yang terakhir adalah salah satu subdirs di dalamnya . Perintah saya adalah find . -name * -print0 | grep -v "scalable" | xargs -0 -I {} cp -a {} /z/test/(memang, saya menggunakan MSYS2, jadi benar-benar masuk /mingw64/share/icons/Adwaita, tapi saya tidak bisa melihat bagaimana ini adalah kesalahan MSYS2)
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

Belum dicoba...

Steve Lazaridis
sumber
Ini salah. Beberapa masalah: Seperti yang ditulis, itu akan menyalin file yang tidak seharusnya dikecualikan beberapa kali (jumlah item yang akan dikecualikan yang dalam hal ini adalah 4). Bahkan jika Anda mencoba menyalin 'foo', item pertama dalam daftar kecualikan, itu masih akan disalin ketika Anda sampai ke x = bar dan saya masih foo. Jika Anda bersikeras melakukan ini tanpa alat yang sudah ada sebelumnya (misalnya rsync), pindahkan salinan ke pernyataan if di luar loop 'for x in ...' dan buat loop 'for x ...' ubah pernyataan logis dalam file salin if (true). Ini akan menghentikan Anda dari menyalin beberapa kali.
Eric Bringley
0

terinspirasi oleh jawaban @ SteveLazaridis, yang akan gagal, berikut adalah fungsi shell POSIX - cukup salin dan tempel ke file yang bernama cpxdi yout Anda $PATHdan buat itu executable ( chmod a+x cpr). [Sumber sekarang dikelola di GitLab saya .

#!/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 "$@"

Contoh penggunaan

EXCLUDE="
.git
my_secret_stuff
"
cpr "$HOME/my_stuff" "/media/usb" "$EXCLUDE"
go2null
sumber
Tampaknya tidak membantu untuk mengatakan bahwa jawaban seseorang "akan gagal" tanpa menjelaskan apa yang salah dengan itu dan bagaimana Anda memperbaikinya ...
underscore_d
@underscore_d: benar, di belakang, esp karena sekarang saya tidak bisa mengingat apa yang gagal :-(
go2null
Banyak hal: (1) menyalin file beberapa kali dan (2) logikanya masih menyalin file yang akan dikecualikan. Jalankan melalui loop menggunakan i = foo: itu akan disalin 3 kali daripada 4 untuk file lain misalnya i = test.txt.
Eric Bringley
1
terima kasih @EricBringley untuk mengklarifikasi kekurangan jawaban Steve. (Dia mengatakan itu belum diuji .)
go2null