12. dosya dışındaki her şeyi kaldır


14

Dosya adı biçiminde birkaç bin dosyam var.12345.end. Sadece her 12. dosyayı saklamak istiyorum, bu yüzden file.00012.end, file.00024.end ... file.99996.end ve diğer her şeyi silin.

Dosyaların dosya adlarında daha önce de sayılar olabilir ve normalde şu biçimdedir: file.00064.name.99999.end

Bash kabuğu kullanıyorum ve dosyalar üzerinde nasıl döngü yapılacağını anlayamıyorum ve sonra numarayı çıkarıyorum ve number%%12=0 eğer dosyayı silip silemediğini kontrol edemiyorum. Biri bana yardım edebilir mi?

Teşekkürler Dorina


Dosyanın numarası sadece dosya adına mı bağlı?
Arronical

Ayrıca, dosyalar her zaman 5 basamaklıdır ve sonek ile önek her zaman aynı mıdır?
16:31

Evet her zaman 5 basamaklıdır. İlk sorunuzun doğru olup olmadığından emin değilim. Farklı dosya adlarına sahip dosyalar farklıdır ve sayıları 00012, 00024 vb. Olan bu özel dosyalara ihtiyacım var.
Dorina

3
@Dorina lütfen sorunuzu düzenleyin ve netleştirin. Her şeyi değiştirir!
terdon

2
Ve hepsi aynı dizinde, değil mi?
Sergiy Kolodyazhnyy

Yanıtlar:


18

İşte bir Perl çözümü. Binlerce dosya için bu çok daha hızlı olmalıdır:

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

Hangi daha da yoğunlaştırılabilir:

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

Çok fazla dosyanız varsa ve basit dosyayı kullanamıyorsanız, *aşağıdakine benzer bir şey yapabilirsiniz:

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

Hız gelince, bu yaklaşımın ve diğer cevaplardan birinde sağlanan kabuk yaklaşımının bir karşılaştırması:

$ 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

Gördüğünüz gibi, fark beklendiği gibi muazzam .

açıklama

  • -eBasitçe anlatıyor perlkomut satırında verilen komut dosyasını çalıştırın.
  • @ARGVbetiğe verilen tüm bağımsız değişkenleri içeren özel bir değişkendir. Verdiğimiz için *, geçerli dizindeki tüm dosyaları (ve dizinleri) içerecektir.
  • grepDosya adları listesinde arama ve sayı dizisi, bir nokta ve eşleşen herhangi arayacaktır end( /(\d+)\.end/).

  • Sayılar ( \d) bir yakalama grubunda (parantez) bulunduğundan, olarak kaydedilirler $1. Böylece, grepbu sayının 12'nin katları olup olmadığını kontrol eder ve eğer değilse, dosya adı döndürülür. Başka bir deyişle, dizi @badsilinecek dosyaların listesini tutar.

  • Daha sonra unlink()dosyaları kaldıran (ancak dizinleri değil) liste aktarılır .


12

Dosya adlarınızın biçimde olduğu göz önüne alındığında, öncelikle file.00064.name.99999.endnumaramız dışındaki her şeyi kesmemiz gerekir. Bunu foryapmak için bir döngü kullanacağız .

Ayrıca Bash kabuğuna baz 10'u kullanmasını söylemeliyiz, çünkü Bash aritmetiği onlara 0 ile başlayan sayıları taban 8 olarak ele alır, bu da bizim için işleri bozar.

Bir komut dosyası olarak, dosyaları içeren dizinde kullanıldığında başlatılacak:

#!/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

Ya da aynı şeyi yapmak için bu çok uzun çirkin komutu kullanabilirsiniz:

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

Tüm bölümleri açıklamak için:

  • for f in ./* geçerli dizindeki her şey için anlamına gelir, do .... Bu $ f değişkeni olarak bulunan her dosya veya dizini ayarlar.
  • if [[ -f "$f" ]]Bulunan öğenin bir dosya olup olmadığını kontrol eder, eğer echo "$f is not...parçayı atlamazsak, dizinleri yanlışlıkla silmeye başlamıyoruz demektir.
  • file="${f%.*}"$ file değişkenini, dosya adının sondan sonra ne olursa olsun kırpmasını ayarlar ..
  • if [[ $((10#${file##*.} % 12)) -eq 0 ]]ana Aritmetik devreye girer. Dosya ${file##*.}adımızda sondan önceki her şeyi .uzantısız keser. $(( $num % $num2 ))Bash aritmetiğinin modulo işlemini kullanacağı sözdizimidir 10#, başlangıçta Bash'e bu sinir bozucu 0'larla başa çıkmak için temel 10'u kullanmasını söyler. $((10#${file##*.} % 12))sonra dosya adları numaramızın geri kalanını 12'ye -ne 0böler. Geri kalanın sıfıra "eşit olmadığını" kontrol eder.
  • Geri kalan 0'a eşit değilse, dosya ile silinir rmkomuta, değiştirmek isteyebilirsiniz rmile echoilk beklendiği dosyaları silmek için olsun kontrol etmek için, bu çalıştırırken.

Bu çözüm özyinelemesizdir, yani yalnızca geçerli dizindeki dosyaları işleyecek, herhangi bir alt dizine girmeyecektir.

ifİle ifadesi echoolarak dizinlere hakkında uyarmak için komuta gerçekten gerekli değildir rmbu yüzden onları silin dizinleri şikayet ve olmayacaktır başlıbaşına:

#!/bin/bash

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

Veya

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

Doğru da çalışacaktır.


5
rmBirkaç bin kez aramak oldukça yavaş olabilir. Ben önermek echodosya yerine adı ve boruya döngünün çıkışını xargs rm(gerektiği gibi eklenti seçenekleri): for f in *; do if ... ; then echo "$f"; fi; done | xargs -rd '\n' -- rm --.
David Foerster

Önerilen hız iyileştirmenizi eklemek için düzenledim.
Arronical

Aslında 55999 dosya içeren bir dizinde test ettikten sonra, orijinal sürüm xargs2 dakika 48 saniye aldı , sürüm 5 dakika 1 saniye sürdü. Bu echo@DavidFoerster üzerindeki ek yük nedeniyle olabilir mi?
Arronical

Garip. 60.000 dosya için 0m0.659s / 0m0.545s / 0m0.380s (gerçek / kullanıcı / sys) ile time { for f in *; do echo "$f"; done | xargs rm; }1m11.450s / 0m10.695s / 0m16.800s değerlerini time { for f in *; do rm "$f"; done; }bir tmpfs ile alırım . Bash v4.3.11, Çekirdek v4.4.19.
David Foerster

6

Her 12 numarayı içeren adlar oluşturmak için Bash parantez genişletmesini kullanabilirsiniz. Biraz test verisi oluşturalım

$ 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

Sonra aşağıdakileri kullanabiliriz

$ 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

Büyük miktarda dosya için umutsuzca yavaş çalışıyor - binlerce isim üretmek zaman ve hafıza gerektiriyor - bu yüzden bu etkili çözüm daha çok bir hile.


Bu bir kod golf severim.
David Foerster

1

Biraz uzun ama aklıma gelen şey bu.

 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

Açıklama: Her 12 dosyayı on bir kez silin.


0

Tüm alçakgönüllülükte bu çözümün diğer cevaptan çok daha güzel olduğunu düşünüyorum:

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

Biraz açıklama: Önce ile birlikte bir dosya listesi oluştururuz find. Adı biten .endve 1 derinliğinde olan tüm dosyaları alırız (yani, herhangi bir alt klasörde değil, doğrudan çalışma dizinindedir. Alt klasör yoksa bunu dışarıda bırakabilirsiniz). Çıktı listesi alfabetik olarak sıralanacaktır.

Daha sonra bu listeyi , satır numarası olan awközel değişkeni kullandığımız yere NRbağlarız. Her 12nci dosyayı, dosyaları nereye yazdırarak dışarıda bırakıyoruz NR%12 != 0. awkKomut için kısaltılabilir awk 'NR%12'modülo operatörünün sonucu mantıksal bir değer olarak yorumlanır alır ve çünkü {print}örtük zaten yapılır.

Şimdi silinmesi gereken dosyaların bir listesi var, bu da xargs ve rm ile yapabiliriz. xargsverilen komutu ( rm) standart girdi ile bağımsız değişken olarak çalıştırır.

Çok fazla dosyanız varsa, 'argüman listesi çok uzun' gibi bir şey söyleyerek hata alırsınız (makinemde sınır 256 kB ve POSIX için gereken minimum değer 4096 bayttır). Bu, -n 100her 100 kelimede (satırlar değil, dosya adlarınızda boşluk varsa dikkat edilmesi gereken bir şey) bağımsız değişkenleri bölen ve her rmbiri yalnızca 100 bağımsız değişkeni olan ayrı bir komut yürüten bayrak tarafından önlenebilir .


3
Yaklaşımınızla ilgili birkaç sorun var: -depthdaha önce olması gerekiyor -name; ii) dosya adlarından herhangi biri boşluk içeriyorsa bu başarısız olur; iii) dosyaların artan sayısal sırayla listeleneceğini varsayıyorsunuz (bunun awkiçin test ettiğiniz şey), ancak bu neredeyse kesinlikle böyle olmayacaktır. Bu nedenle, rastgele bir dosya kümesi silinir.
terdon

oh d'! Çok haklısın, benim hatam (yorum düzenlendi). Yanlış yerleşim nedeniyle hatayı aldım ve hatırlamadım -depth. Yine de, buradaki sorunların en azıydı, en önemlisi OP'nin istediği dosyaları değil, rastgele bir dosya kümesini silmenizdir.
terdon

Oh, ve hayır, -depthbir değer almıyor ve düşündüğünün tam tersini yapıyor. Bkz. man find"-Depth Her dizinin içeriğini dizinin kendisinden önce işleme.". Yani bu aslında alt dizinlere inecek ve her yere zarar verecek.
terdon

I) Hem -depth nve -maxdepth nmevcuttur. Birincisi, derinliğin tam olarak n olmasını gerektirir ve ikincisi ile <= n olabilir. II). Evet, bu kötü ama bu özel örnek için endişe yok. find ... -print0 | awk 'BEGIN {RS="\0"}; NR%12 != 0' | xargs -0 -n100 rmBoş byte'ı kayıt ayırıcı olarak (dosya adlarında izin verilmez) kullanan bunu kullanarak düzeltebilirsiniz . III) Bir kez daha, bu durumda varsayım makuldür. Aksi takdirde eklemek olabilir sort -narasına findve awkveya yönlendirme findbir dosyaya ve benzeri sıralama nasıl olursa olsun size.
user593851

3
Ah, muhtemelen OSX kullanıyorsunuz. Bu çok farklı bir uygulama find. Yine de, asıl sorun findsıralı bir liste döndürdüğünü varsaymanızdır . Öyle değil.
terdon

0

Yalnızca bash kullanmak için ilk yaklaşımım: 1. saklamak istediğiniz tüm dosyaları başka bir dizine taşımak (yani, dosya adındaki numarası 12'nin katları olan tüm dosyaları) sonra 2. dizinde kalan tüm dosyaları silmek, sonra 3. sakladığınız 12'den fazla dosyayı bulundukları yere geri koyun. Yani böyle bir şey işe yarayabilir:

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

Yaklaşımı seviyorum, ama filenametutarlı değilse parçayı nasıl üretiyorsunuz ?
Arronical
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.