Hapus semua kecuali setiap file ke-12

14

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

Dorina
sumber
Apakah jumlah file hanya tergantung pada nama file?
Arronical
Juga, apakah file selalu memiliki 5 digit, dan apakah akhiran dan awalan selalu sama?
Arronical
Ya selalu 5 digit. Saya tidak yakin apakah pertanyaan pertama Anda benar. File dengan nama file yang berbeda berbeda, dan saya perlu file khusus ini yang kebetulan memiliki nomor 00012, 00024 dll.
Dorina
3
@Dorina tolong edit pertanyaan Anda dan jelaskan. Itu mengubah segalanya!
terdon
2
Dan mereka semua berada di direktori yang sama, bukan?
Sergiy Kolodyazhnyy

Jawaban:

18

Inilah solusi Perl. Ini harusnya jauh lebih cepat untuk ribuan file:

perl -e '@bad=grep{/(\d+)\.end/ && $1 % 12 != 0}@ARGV; unlink @bad' *

Yang selanjutnya dapat diringkas menjadi:

perl -e 'unlink grep{/(\d+)\.end/ && $1 % 12 != 0}@ARGV;' *

Jika Anda memiliki terlalu banyak file dan tidak dapat menggunakan yang sederhana *, Anda dapat melakukan sesuatu seperti:

perl -e 'opendir($d,"."); unlink grep{/(\d+)\.end/ && $1 % 12 != 0} readdir($dir)'

Adapun kecepatan, inilah perbandingan pendekatan ini dan shell yang disediakan di salah satu jawaban lain:

$ touch file.{01..64}.name.{00001..01000}.end
$ ls | wc
  64000   64000 1472000
$ time for f in ./* ; do file="${f%.*}"; if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then rm "$f"; fi; done

real    2m44.258s
user    0m9.183s
sys     1m7.647s

$ touch file.{01..64}.name.{00001..01000}.end
$ time perl -e 'unlink grep{/(\d+)\.end/ && $1 % 12 != 0}@ARGV;' *

real    0m0.610s
user    0m0.317s
sys     0m0.290s

Seperti yang Anda lihat, perbedaannya sangat besar, seperti yang diharapkan .

Penjelasan

  • Ini -ehanya memberitahu perluntuk menjalankan skrip yang diberikan pada baris perintah.
  • @ARGVadalah 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 grepakan mencari melalui daftar nama file dan mencari apapun yang cocok dengan string angka, titik dan end( /(\d+)\.end/).

  • Karena angka-angka ( \d) berada dalam grup tangkap (tanda kurung), mereka disimpan sebagai $1. Jadi grepkemudian akan memeriksa apakah nomor itu adalah kelipatan dari 12 dan, jika tidak, nama file akan dikembalikan. Dengan kata lain, array @badmenyimpan daftar file yang akan dihapus.

  • Daftar ini kemudian diteruskan ke unlink()mana menghapus file (tetapi bukan direktori).

terdon
sumber
12

Mengingat nama file Anda dalam format file.00064.name.99999.end, pertama-tama kami harus memangkas semuanya kecuali nomor kami. Kami akan menggunakan forloop 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:

#!/bin/bash

for f in ./*
do
  if [[ -f "$f" ]]; then
    file="${f%.*}"
    if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then
      rm "$f"
    fi
  else
    echo "$f is not a file, skipping."
  fi
done

Atau Anda dapat menggunakan perintah jelek yang sangat panjang ini untuk melakukan hal yang sama:

for f in ./* ; do if [[ -f "$f" ]]; then file="${f%.*}"; if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then rm "$f"; fi; else echo "$f is not a file, skipping."; fi; done

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 ke echo "$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 0memeriksa apakah sisanya "tidak sama" dengan nol.
  • Jika sisanya tidak sama dengan 0, file dihapus dengan rmperintah, Anda mungkin ingin mengganti rmdengan echosaat 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 ifpernyataan dengan echoperintah untuk memperingatkan tentang direktori tidak benar-benar diperlukan karena rmpada itu sendiri akan mengeluh tentang direktori, dan tidak menghapusnya, sehingga:

#!/bin/bash

for f in ./*
do
  file="${f%.*}"
  if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then
    rm "$f"
  fi
done

Atau

for f in ./* ; do file="${f%.*}"; if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then rm "$f"; fi; done

Akan bekerja dengan benar juga.

Arronikal
sumber
5
Memanggil rmbeberapa ribu kali bisa sangat lambat. Saya sarankan untuk echonama file bukan dan pipa output dari loop untuk xargs rm(pilihan add sesuai kebutuhan): for f in *; do if ... ; then echo "$f"; fi; done | xargs -rd '\n' -- rm --.
David Foerster
Saya telah mengedit untuk menyertakan peningkatan kecepatan yang disarankan.
Arronical
Sebenarnya setelah pengujian pada direktori dengan 55999 file, versi aslinya mengambil 2mins 48secs, xargsversi mengambil 5mins 1 detik. Mungkinkah ini disebabkan oleh overhead pada echo@DavidFoerster?
Arronical
Aneh. Untuk 60.000 file, saya mendapatkan 0m0.659s / 0m0.545s / 0m0.380s (real / user / sys) dengan time { for f in *; do echo "$f"; done | xargs rm; }vs. 1m11.450s / 0m10.695s / 0m10.695s / 0m16.800s dengan time { for f in *; do rm "$f"; done; }tmpfs. Bash adalah v4.3.11, Kernel adalah v4.4.19.
David Foerster
6

Anda dapat menggunakan ekspansi braket Bash untuk menghasilkan nama yang berisi setiap angka ke-12. Mari kita buat beberapa data uji

$ touch file.{0..9}{0..9}{0..9}{0..9}{0..9}.end # create test data
$ mv file.00024.end file.00024.end.name.99999.end # testing this form of filenames

Maka kita bisa menggunakan yang berikut ini

$ ls 'file.'{00012..100..12}* # print these with numbers less than 100
file.00012.end                 file.00036.end  file.00060.end  file.00084.end
file.00024.end.name.99999.end  file.00048.end  file.00072.end  file.00096.end
$ rm 'file.'{00012..100000..12}* # do the job

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.

Nykakin
sumber
Saya suka golf code yang satu ini.
David Foerster
1

Agak lama, tapi itulah yang terlintas di benakku.

 for num in $(seq 1 1 11) ; do
     for sequence in $(seq -f %05g $num 12 99999) ; do
         rm file.$sequence.end.99999;
     done
 done

Penjelasan: Hapus setiap file ke-12 sebelas kali.

Terrik
sumber
0

Dalam segala kerendahan hati saya pikir solusi ini jauh lebih baik daripada jawaban yang lain:

find . -name '*.end' -depth 1 | awk 'NR%12 != 0 {print}' | xargs -n100 rm

Penjelasan kecil: Pertama kita buat daftar file dengan find. Kami mendapatkan semua file yang namanya berakhir dengan .enddan 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 khusus NRyang merupakan nomor baris. Kami meninggalkan setiap file ke-12 dengan mencetak file di mana NR%12 != 0. The awkperintah dapat disingkat menjadi awk '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. xargsmenjalankan 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 100flag, yang membagi argumen setiap 100 kata (bukan baris, sesuatu yang harus diperhatikan jika nama file Anda memiliki spasi) dan menjalankan rmperintah terpisah , masing-masing dengan hanya 100 argumen.

pengguna593851
sumber
3
Ada beberapa masalah dengan pendekatan Anda: -depthperlu 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 Anda awkuji), tetapi ini hampir pasti tidak akan terjadi. Oleh karena itu, ini akan menghapus satu set file acak.
terdon
d'oh! Anda benar, salah saya (komentar diedit). Saya mendapatkan kesalahan karena penempatan yang salah dan tidak ingat -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.
terdon
Oh, dan tidak, -depthtidak mengambil nilai dan itu berlawanan dengan apa yang Anda pikirkan. Lihat man find: "-depth Memproses isi setiap direktori sebelum direktori itu sendiri.". Jadi ini akan benar-benar turun ke subdirektori dan mendatangkan malapetaka di semua tempat.
terdon
I) Keduanya -depth ndan -maxdepth nada. 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 menggunakan find ... -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 menyisipkan sort -nantara finddan awk, atau mengalihkan findke file dan mengurutkannya sesuka Anda.
user593851
3
Ah, Anda mungkin menggunakan OSX saat itu. Itu implementasi yang sangat berbeda find. Namun, sekali lagi, masalah utama adalah Anda berasumsi bahwa findmengembalikan daftar yang diurutkan. Tidak.
terdon
0

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:

cd dir_containing_files
mkdir keep_these_files
n=0
while [ "${n}" -lt 99999 ]; do
  padded_n="`echo -n "00000${n}" | tail -c 5`"
  mv "filename${padded_n}.end" keep_these_files/
  n=$[n+12]
done
rm filename*.end
mv keep_these_files/* .
rmdir keep_these_files
delt
sumber
Saya suka pendekatannya, tetapi bagaimana Anda menghasilkan filenamebagian jika tidak konsisten?
Arronical