Hapus semua file kecuali di subdirektori tertentu dengan find

11

Saya ingin secara rekursif menghapus semua file yang tidak diakses dalam folder sementara a, kecuali semua file dalam subfolder b.

find a \( -name b -prune \) -o -type f -delete

Namun, saya mendapatkan pesan kesalahan:

find: Tindakan -delete secara otomatis mengaktifkan -depth, tetapi -prune tidak melakukan apa-apa ketika -depth berlaku. Jika Anda tetap ingin melanjutkan, cukup gunakan opsi -depth secara eksplisit.

Menambahkan -depthmenyebabkan semua file bdimasukkan, yang tidak boleh terjadi.

Adakah yang tahu cara aman untuk membuat ini bekerja?

sebagainya
sumber
@ MichaelKjörling: Saya telah melihat extglob, tetapi bagaimana Anda memasukkan semuanya di bawah akecuali a/b?
sebagainyarin
Tidak akan cd a && ls -d !(b/*)bekerja (Untuk melakukannya, hanya rm -rdaripada ls -d.)
CVn
Saran Anda menghapus subfolder. Saya ingin menjaga folder tetap utuh. Saya ingin menemukan dan menghapus semua file di pohon di bawah a(kecuali file di bawah a/b).
sebagainyarin
Jadi lewati saja -rke rm. Sepertinya apa yang Anda tanyakan cukup mudah dijawab dengan menggunakan bash's globbing yang diperluas, dan kemudian apa yang Anda lakukan dengan hasil globbing itu terserah Anda.
CVn
@ MichaelKjörling Hanya karena kedua masalah memiliki solusi yang hampir tidak menyerupai tidak membuat pertanyaan menjadi duplikat. Sebagian besar solusi untuk masing-masing dari dua masalah tidak menyelesaikan masalah lainnya.
Gilles 'SANGAT berhenti menjadi jahat'

Jawaban:

13

TL; DR: cara terbaik adalah menggunakan -exec rmalih-alih -delete.

find a \( -name b -prune \) -o -type f -exec rm {} +

Penjelasan:

Mengapa menemukan mengeluh ketika Anda mencoba untuk menggunakan -deletedengan -prune?

Jawaban singkat: karena -deletemenyiratkan -depthdan -depthmembuat -prunetidak efektif.

Sebelum kita sampai pada jawaban panjang, pertama-tama amati perilaku menemukan dengan dan tanpa -depth:

$ find foo/
foo/
foo/f1
foo/bar
foo/bar/b2
foo/bar/b1
foo/f2

Tidak ada jaminan tentang pesanan dalam satu direktori. Tetapi ada jaminan bahwa direktori diproses sebelum isinya. Catat foo/sebelum foo/*dan foo/barsebelum apa pun foo/bar/*.

Ini dapat dibalik dengan -depth.

$ find foo/ -depth
foo/f2
foo/bar/b2
foo/bar/b1
foo/bar
foo/f1
foo/

Perhatikan bahwa sekarang semua foo/*muncul sebelumnya foo/. Sama dengan foo/bar.

Jawaban yang lebih panjang:

  • -prunemencegah menemukan turun ke direktori. Dengan kata lain -prunemelompati isi direktori. Dalam kasus Anda, -name b -prunemencegah menemukan turun ke direktori apa pun dengan nama b.
  • -depthmake find untuk memproses isi direktori sebelum direktori itu sendiri. Itu berarti pada saat ditemukan dapat memproses entri direktori bisinya telah diproses. Dengan demikian -prunetidak efektif dengan -depthefeknya.
  • -deletetersirat -depthsehingga dapat menghapus file pertama dan kemudian direktori kosong. -deletemenolak untuk menghapus direktori yang tidak kosong. Saya kira itu akan mungkin untuk menambahkan pilihan untuk kekuatan -deleteuntuk menghapus direktori yang tidak kosong dan / atau untuk mencegah -deletemenyiratkan -depth. Tapi itu cerita lain.

Ada cara lain untuk mencapai apa yang Anda inginkan:

find a -not -path "*/b*" -type f -delete

Ini mungkin atau mungkin tidak mudah diingat.

Perintah ini masih turun ke direktori bdan memproses setiap file di dalamnya hanya untuk -notmenolaknya. Ini bisa menjadi masalah kinerja jika direktori bsangat besar.

-pathbekerja berbeda dari -name. -namehanya cocok dengan nama (file atau direktori) saat -pathcocok dengan seluruh jalur. Misalnya mengamati jalannya /home/lesmana/foo/bar. -name -barakan cocok karena namanya bar. -path "*/foo*"akan cocok karena string /fooada di jalur. -pathmemiliki beberapa seluk yang harus Anda pahami sebelum menggunakannya. Baca halaman manual finduntuk lebih jelasnya.

Berhati-hatilah karena ini bukan 100% sangat mudah. Ada kemungkinan "false positive". Cara perintah ditulis di atas akan melewati file apa pun yang memiliki direktori induk yang namanya dimulai dengan b(positif). Tapi itu juga akan melewatkan file apa pun yang namanya dimulai dengan bterlepas dari posisi di pohon (false positive). Ini dapat diperbaiki dengan menulis ekspresi yang lebih baik daripada "*/b*". Itu dibiarkan sebagai latihan untuk pembaca.

Saya berasumsi bahwa Anda menggunakan adan bsebagai placeholder dan nama asli lebih mirip allosaurusdan brachiosaurus. Jika Anda meletakkan brachiosaurusdi tempat bmaka jumlah positif palsu akan berkurang drastis.

Setidaknya positif palsu tidak akan dihapus, sehingga tidak akan tragis. Selanjutnya, Anda dapat memeriksa positif palsu dengan terlebih dahulu menjalankan perintah tanpa -delete(tapi ingat untuk menempatkan yang tersirat -depth) dan memeriksa hasilnya.

find a -not -path "*/b*" -type f -depth
lesmana
sumber
-not -pathitu masalahnya! Terima kasih untuk penjelasannya!
sebagainyarin
1
Beberapa penjelasan mengapa -not -pathbekerja sementara -prunetidak akan membantu. Mengapa bisa -not -pathhidup berdampingan dengan -depth?
Faheem Mitha
3

Gunakan saja rmalih-alih -delete:

find a -name b -prune -o -type f -exec rm -f {} +
Stéphane Chazelas
sumber
1
Bisakah Anda menguraikan mengapa itu rmberhasil dan deletetidak?
Faheem Mitha
1
Oh, saya kira mungkin karena "-delete menolak untuk menghapus direktori yang tidak kosong.", Mengutip @lesmana. Jadi menolak untuk menghapus direktori yang tidak kosong. Tetapi rmtidak memiliki masalah itu. Namun, bagaimanapun juga, elaborasi akan menjadi hal yang baik.
Faheem Mitha
@FaheemMitha, jawabannya ada di pertanyaan. -deletetersirat -depth, yang jelas tidak bisa bekerja dengan -prune. -pathberfungsi, tetapi tidak berhenti findturun di direktori yang tidak perlu dijelajahi.
Stéphane Chazelas
0

Jawaban dan penjelasan di atas sangat membantu.

Saya menggunakan solusi "-exec rm {} +" atau "-not -path ... -delete ', tetapi itu bisa lebih lambat dari" find ... -delete ". Saya telah melihat" find ... -hapus "jalankan 5x lebih cepat dari" -exec rm {} + "pada direktori yang dalam pada sistem file NFS.

Solusi '-not path "memiliki overhead yang jelas untuk melihat semua file di direktori yang dikecualikan dan di bawah.

"Find .. -exec rm {} +" memanggil rm yang melakukan panggilan sistem:

fstatat(AT_FDCWD, path...); 
unlinkat(AT_FDCWD, path, 0)

"Find -delete" melakukan panggilan sistem:

 fd=open(dir,...);
 fchdir(fd); 
 fstatat(AT_FDCWD, filename,...)
 unlinkat(dirfd, filename,...)

Jadi "-exec rm {} +" perintah rm melakukan path lengkap ke inode lookup dua kali dua kali per file, tetapi "find -delete" melakukan stat dan putuskan tautan nama file di direktori saat ini. Itu adalah kemenangan besar ketika Anda menghapus banyak file dalam satu direktori.

(mode merengek aktif (maaf))

Sepertinya desain interaksi antara -depth, -delete dan -prune tidak perlu menghilangkan cara paling efisien untuk melakukan tindakan umum "menghapus file kecuali yang ada di direktori -prune"

Kombinasi "-type f -delete" harus dapat dijalankan tanpa -depth karena tidak mencoba untuk menghapus direktori. Atau, jika "find" memiliki tindakan "-deletefile" yang mengatakan jangan hapus direktori, -depth tidak perlu diimplikasikan.

Perintah xargs atau find -exec to rm dapat dipercepat jika rm memiliki opsi untuk mengurutkan nama file, membuka direktori, dan melakukan unlinkat (dir_fd, nama file) alih-alih memutus tautan jalur lengkap. Itu sudah melakukan unlinkat (dir_fd, nama file) ketika berulang melalui direktori dengan opsi -r.

(mode merengek mati)

Donald Mears
sumber