Saya memiliki beberapa ribu file dalam format filename.12345.end. Saya hanya ingin menyimpan setiap file ke-12, jadi file.00012.end, file.00024.end ... file.99996.end dan hapus semua yang lainnya.
File-file ini mungkin juga memiliki angka di depan nama file mereka, dan biasanya dalam bentuk: file.00064.name.99999.end
Saya menggunakan Bash shell dan tidak dapat menemukan cara untuk mengulang file dan kemudian keluar nomor dan memeriksa apakah itu number%%12=0
menghapus file jika tidak. Ada yang bisa bantu saya?
Terima kasih, Dorina
Jawaban:
Inilah solusi Perl. Ini harusnya jauh lebih cepat untuk ribuan file:
Yang selanjutnya dapat diringkas menjadi:
Jika Anda memiliki terlalu banyak file dan tidak dapat menggunakan yang sederhana
*
, Anda dapat melakukan sesuatu seperti:Adapun kecepatan, inilah perbandingan pendekatan ini dan shell yang disediakan di salah satu jawaban lain:
Seperti yang Anda lihat, perbedaannya sangat besar, seperti yang diharapkan .
Penjelasan
-e
hanya memberitahuperl
untuk menjalankan skrip yang diberikan pada baris perintah.@ARGV
adalah variabel khusus yang berisi semua argumen yang diberikan ke skrip. Karena kita memberikannya*
, itu akan berisi semua file (dan direktori) di direktori saat ini.The
grep
akan mencari melalui daftar nama file dan mencari apapun yang cocok dengan string angka, titik danend
(/(\d+)\.end/)
.Karena angka-angka (
\d
) berada dalam grup tangkap (tanda kurung), mereka disimpan sebagai$1
. Jadigrep
kemudian akan memeriksa apakah nomor itu adalah kelipatan dari 12 dan, jika tidak, nama file akan dikembalikan. Dengan kata lain, array@bad
menyimpan daftar file yang akan dihapus.Daftar ini kemudian diteruskan ke
unlink()
mana menghapus file (tetapi bukan direktori).sumber
Mengingat nama file Anda dalam format
file.00064.name.99999.end
, pertama-tama kami harus memangkas semuanya kecuali nomor kami. Kami akan menggunakanfor
loop untuk melakukan ini.Kita juga perlu memberi tahu shell Bash untuk menggunakan basis 10, karena aritmatika Bash akan memperlakukan mereka angka yang dimulai dengan 0 sebagai basis 8, yang akan mengacaukan segalanya bagi kita.
Sebagai skrip, untuk diluncurkan ketika dalam direktori yang berisi file gunakan:
Atau Anda dapat menggunakan perintah jelek yang sangat panjang ini untuk melakukan hal yang sama:
Untuk menjelaskan semua bagian:
for f in ./*
berarti untuk semua yang ada di direktori saat ini, lakukan .... Ini menetapkan setiap file atau direktori yang ditemukan sebagai variabel $ f.if [[ -f "$f" ]]
memeriksa apakah item yang ditemukan adalah file, jika tidak kita lewati keecho "$f is not...
bagian tersebut, yang berarti kita tidak mulai menghapus direktori secara tidak sengaja.file="${f%.*}"
menetapkan variabel $ file sebagai nama file yang memotong apa pun yang muncul setelah yang terakhir.
.if [[ $((10#${file##*.} % 12)) -eq 0 ]]
Di sinilah aritmatika utama dimulai.${file##*.}
Trim semuanya sebelum yang terakhir.
dalam nama file kami tanpa ekstensi.$(( $num % $num2 ))
adalah sintaks untuk aritmatika Bash untuk menggunakan operasi modulo,10#
pada awalnya memberitahu Bash untuk menggunakan basis 10, untuk berurusan dengan 0s terkemuka yang sial itu.$((10#${file##*.} % 12))
kemudian meninggalkan sisa nomor nama file kami dibagi dengan 12.-ne 0
memeriksa apakah sisanya "tidak sama" dengan nol.rm
perintah, Anda mungkin ingin menggantirm
denganecho
saat pertama kali menjalankan ini, untuk memeriksa apakah Anda mendapatkan file yang diharapkan untuk dihapus.Solusi ini non-rekursif, artinya hanya akan memproses file dalam direktori saat ini, tidak akan masuk ke sub-direktori.
The
if
pernyataan denganecho
perintah untuk memperingatkan tentang direktori tidak benar-benar diperlukan karenarm
pada itu sendiri akan mengeluh tentang direktori, dan tidak menghapusnya, sehingga:Atau
Akan bekerja dengan benar juga.
sumber
rm
beberapa ribu kali bisa sangat lambat. Saya sarankan untukecho
nama file bukan dan pipa output dari loop untukxargs rm
(pilihan add sesuai kebutuhan):for f in *; do if ... ; then echo "$f"; fi; done | xargs -rd '\n' -- rm --
.xargs
versi mengambil 5mins 1 detik. Mungkinkah ini disebabkan oleh overhead padaecho
@DavidFoerster?time { for f in *; do echo "$f"; done | xargs rm; }
vs. 1m11.450s / 0m10.695s / 0m10.695s / 0m16.800s dengantime { for f in *; do rm "$f"; done; }
tmpfs. Bash adalah v4.3.11, Kernel adalah v4.4.19.Anda dapat menggunakan ekspansi braket Bash untuk menghasilkan nama yang berisi setiap angka ke-12. Mari kita buat beberapa data uji
Maka kita bisa menggunakan yang berikut ini
Bekerja sangat lambat untuk sejumlah besar file - butuh waktu dan memori untuk menghasilkan ribuan nama - jadi ini lebih merupakan trik daripada solusi efisien yang sebenarnya.
sumber
Agak lama, tapi itulah yang terlintas di benakku.
Penjelasan: Hapus setiap file ke-12 sebelas kali.
sumber
Dalam segala kerendahan hati saya pikir solusi ini jauh lebih baik daripada jawaban yang lain:
Penjelasan kecil: Pertama kita buat daftar file dengan
find
. Kami mendapatkan semua file yang namanya berakhir dengan.end
dan berada pada kedalaman 1 (artinya, mereka langsung berada di direktori kerja dan bukan di subfolder apa pun. Anda dapat mengabaikannya jika tidak ada subfolder). Daftar output akan diurutkan berdasarkan abjad.Kemudian kita pipa daftar itu ke
awk
, di mana kita menggunakan variabel khususNR
yang merupakan nomor baris. Kami meninggalkan setiap file ke-12 dengan mencetak file di manaNR%12 != 0
. Theawk
perintah dapat disingkat menjadiawk 'NR%12'
, karena hasil dari operator modulo akan ditafsirkan sebagai nilai boolean dan{print}
secara implisit dilakukan pula.Jadi sekarang kita memiliki daftar file yang perlu dihapus, yang bisa kita lakukan dengan xargs dan rm.
xargs
menjalankan perintah yang diberikan (rm
) dengan input standar sebagai argumen.Jika Anda memiliki banyak file, Anda akan mendapatkan kesalahan mengatakan sesuatu seperti 'daftar argumen terlalu panjang' (pada mesin saya yang membatasi adalah 256 kB, dan minimum yang diperlukan oleh POSIX adalah 4096 byte). Ini dapat dihindari oleh
-n 100
flag, yang membagi argumen setiap 100 kata (bukan baris, sesuatu yang harus diperhatikan jika nama file Anda memiliki spasi) dan menjalankanrm
perintah terpisah , masing-masing dengan hanya 100 argumen.sumber
-depth
perlu sebelum-name
; ii) ini akan gagal jika salah satu nama file berisi spasi putih; iii) Anda mengasumsikan file akan terdaftar dalam urutan numerik naik (itulah yang Andaawk
uji), tetapi ini hampir pasti tidak akan terjadi. Oleh karena itu, ini akan menghapus satu set file acak.-depth
. Namun, itu adalah masalah yang paling kecil di sini, yang paling penting adalah Anda menghapus satu set file acak dan bukan yang diinginkan OP.-depth
tidak mengambil nilai dan itu berlawanan dengan apa yang Anda pikirkan. Lihatman find
: "-depth Memproses isi setiap direktori sebelum direktori itu sendiri.". Jadi ini akan benar-benar turun ke subdirektori dan mendatangkan malapetaka di semua tempat.-depth n
dan-maxdepth n
ada. Yang pertama membutuhkan kedalaman menjadi tepat n, dan dengan yang terakhir bisa menjadi <= n. II). Ya, itu buruk tapi untuk contoh khusus ini bukan masalah. Anda bisa memperbaikinya dengan menggunakanfind ... -print0 | awk 'BEGIN {RS="\0"}; NR%12 != 0' | xargs -0 -n100 rm
, yang menggunakan byte nol sebagai pemisah rekaman (yang tidak diizinkan dalam nama file). III) Sekali lagi, dalam hal ini anggapannya masuk akal. Kalau tidak, Anda bisa menyisipkansort -n
antarafind
danawk
, atau mengalihkanfind
ke file dan mengurutkannya sesuka Anda.find
. Namun, sekali lagi, masalah utama adalah Anda berasumsi bahwafind
mengembalikan daftar yang diurutkan. Tidak.Untuk hanya menggunakan bash, pendekatan pertama saya adalah: 1. memindahkan semua file yang ingin Anda simpan ke direktori lain (mis. Semua yang jumlahnya dalam nama file adalah kelipatan 12) kemudian 2. menghapus semua file yang tersisa dalam direktori, lalu 3. letakkan banyak dari 12 file yang Anda simpan di tempat sebelumnya. Jadi sesuatu seperti ini mungkin berhasil:
sumber
filename
bagian jika tidak konsisten?