Dosyaları sayan bir bash komutu var mı?


182

Bir kalıpla eşleşen dosya sayısını sayan bir bash komutu var mı?

Örneğin, bu desenle eşleşen bir dizindeki tüm dosyaların sayısını almak istiyorum: log*

Yanıtlar:


243

Bu basit tek astar, sadece bash'da değil, herhangi bir kabukta çalışmalıdır:

ls -1q log* | wc -l

ls -1q, boşluk veya satırsonu gibi özel karakterler içeriyor olsalar bile, dosya başına bir satır verir.

Çıktı, hat sayısını sayan wc -l'ye bağlanır.


10
Ben kullanmak olmaz -l, çünkü bu stat(2)her dosyada gerektirir ve sayma amacıyla hiçbir şey ekler.
camh

12
lsBir çocuk süreci yarattığı için kullanmam . log*kabuk tarafından genişletildi, değil ls, bu yüzden basit bir echoyapardı.
cdarke

2
Boşluk veya özel karakter içeren dosya adlarınız varsa yankı çalışmaz.
Daniel

4
@WalterTross Bu doğrudur (verimlilik, asıl sorunun bir gereği değildi). Ben de sadece çıkış terminali olmasa bile, -q satırsonu dosyaları ile ilgilenir bulundu. Ve bu bayraklar test ettiğim tüm platformlar ve mermiler tarafından destekleniyor. Cevabı güncelleme, giriş için teşekkürler ve camh!
Daniel

3
logsSöz konusu dizinde çağrılan bir dizin varsa , o günlük dizininin içeriği de sayılır. Bu muhtemelen kasıtlı değildir.
mogsie

54

Bunu güvenli bir şekilde yapabilirsiniz (yani boşluklu veya \nadlarındaki dosyalar tarafından hatalandırılmaz ) bash ile:

$ shopt -s nullglob
$ logfiles=(*.log)
$ echo ${#logfiles[@]}

Sen etkinleştirmeniz gerekir nullglobdeğişmeziyle alamadım böylece *.logde $logfiles dizinin hiçbir dosya eşleşirse. (Bkz "geri alma" Bir 'dizi-x'? Nasıl güvenle sıfırlamak için nasıl örnekler için.)


2
Belki de açıkça bunun sadece bir Bash cevabı olduğunu, özellikle de henüz tamamen sh ve bash arasındaki farkı
üçlü

Ayrıca, shopt -u nullglobeğer nullglobayarlanmamışsa final atlanmalı, o zaman başladınız.
üçlü

Not: *.logSadece yerine *koymak dizinleri sayar. Numaralandırmak istediğiniz dosyalar geleneksel adlandırma kuralına sahipse name.extension, kullanın *.*.
AlainD

52

Burada bir sürü cevap var, ama bazıları dikkate almıyor

  • boşluk, yeni satır veya kontrol karakteri içeren dosya adları
  • kısa çizgilerle başlayan dosya adları (adlı dosyayı hayal edin -l)
  • nokta ile başlayan gizli dosyalar (glob *.logyerinelog*
  • glob ile eşleşen dizinler (örneğin logs, eşleşen bir dizin log*)
  • boş dizinler (yani sonuç 0'dır)
  • aşırı büyük dizinler (hepsini listelemek hafızayı tüketebilir)

İşte hepsini halledecek bir çözüm:

ls 2>/dev/null -Ubad1 -- log* | wc -l

Açıklama:

  • -Ulsgirişlerin sıralanmamasına neden olur , yani tüm dizin listesinin belleğe yüklenmesi gerekmez
  • -bC olmayan stil karakterleri için C-tarzı çıkış karakterleri yazdırır, bu da özellikle satırsonu olarak yazdırılır \n.
  • -a tüm dosyaları, hatta gizli dosyaları yazdırır (glob olduğunda kesinlikle gerekli değildir) log* hiçbir gizli dosya gerektirmediğinde )
  • -dListeye girişiminde bulunulmadan dizinlere dışarı baskılar içeriğini nedir dizine ait lsnormalde yapacağını
  • -1 bir sütunda olduğundan emin olur (ls bunu bir boruya yazarken otomatik olarak yapar, bu yüzden kesinlikle gerekli değildir)
  • 2>/dev/null0 günlük dosyası varsa, hata iletisini yok saymak için stderr'i yeniden yönlendirir. ( Bunun yerine tüm çalışma dizinini listelemenize shopt -s nullglobneden olacağını unutmayın ls.)
  • wc -ldizin listesini oluşturulurken kullanır, bu nedenle çıktısı lshiçbir zaman hafızada kalmaz.
  • --Dosya adları, --argüman olarak anlaşılmayacak şekilde ls( log*kaldırıldığında) komuttan ayrılır

Kabuk edecek genişletmek log*o dosyaların bir sürü eğer grep aracılığıyla çalışan öyleyse, hafızayı tüketmesine dosyaların tam listesi daha iyi olduğunu:

ls -Uba1 | grep ^log | wc -l

Bu sonuncusu, çok fazla bellek kullanmadan (büyük bir alt kabuk kullanmasına rağmen) çok büyük dosya dizinlerini işler. -dSadece geçerli dizinin içeriğini listeleyen çünkü, artık gerekli değildir.


48

Yinelemeli arama için:

find . -type f -name '*.log' -printf x | wc -c

wc -cçıkışında karakter sayısını sayar findiken, -printf xsöyler findtek yazdırmak xHer sonuç için.

Özyinelemesiz arama için şunları yapın:

find . -maxdepth 1 -type f -name '*.log' -printf x | wc -c

6
Bile sen boşluklarla dosyaları yok, senin senaryonun başka kullanıcı komut dosyaları başarısız olmasına neden kötü amaçla adlı dosyayı karşılaşabilirsiniz. Ayrıca, StackOverflow'da bununla karşılaşan diğer kişilerin yeni satırlı dosyaları olabilir ve tuzakları bilmeleri gerekebilir.
mogsie

FYI sadece dışarıda kalırsanız -name '*.log', tüm dosyaları sayar, bu da benim kullanım durumum için gerekli olan şeydir. Ayrıca -maxdepth bayrağı son derece kullanışlıdır, teşekkürler!
starmandeluxe

2
İçinde yeni satır bulunan dosya adları varsa, bu yine de yanlış sonuçlar verir. Geçici çözüm kolaydır find; sadece sözlü dosya adından başka bir şey yazdırın.
üçlü

8

Bu soru için kabul edilen cevap yanlış, ama düşük temsilcisi var, bu yüzden ona yorum ekleyemiyorum.

Bu soruya doğru cevap Mat tarafından verilir:

shopt -s nullglob
logfiles=(*.log)
echo ${#logfiles[@]}

Kabul edilen cevapla ilgili sorun, wc -l'nin satırsonu karakter sayısını sayması ve terminale '?' Olarak yazdırsa bile bunları saymasıdır. 'ls -l' çıktısında. Bu, bir dosya adı yeni satır karakteri içerdiğinde kabul edilen yanıtın başarısız olduğu anlamına gelir. Önerilen komutu test ettim:

ls -l log* | wc -l

ve ismi yeni satır karakteri içerecek şekilde kalıpla eşleşen 1 dosya olsa bile hatalı olarak 2 değerini bildirir. Örneğin:

touch log$'\n'def
ls log* -l | wc -l

6

Çok fazla dosyanız varsa ve zarif shopt -s nullglobve bash dizi çözümünü kullanmak istemiyorsanız, dosya adını (yeni satırlar içerebilir) yazdırmazsanız find ve benzerlerini kullanabilirsiniz.

find -maxdepth 1 -name "log*" -not -name ".*" -printf '%i\n' | wc -l

Bu, log * ile eşleşen ve ile başlamayan tüm dosyaları bulur .* . Bulmak onları dahil etmektir.

Bu doğru bir yanıttır ve ona atabileceğiniz her türlü dosya adını işler, çünkü dosya adı hiçbir zaman komutlar arasında aktarılmaz.

Ama shopt nullglobcevap en iyi cevap!


Muhtemelen tekrar cevaplamak yerine orijinal cevabınızı güncellemelisiniz.
qodeninja

Bence findvs kullanarak lssorunu çözmenin iki farklı yolu var. findbir makinede her zaman mevcut değildir, ancak lsgenellikle,
mogsie 18:17

2
Ama sonra findmuhtemelen bir tane domuz yağı kutusu da bu seçeneklerin hepsine sahip değil ls.
üçlü

1
Ayrıca dikkat edin-maxdepth 1
tripleee

1
Bu çözümün, gizli dizinlerin içindeki dosyaları sayar. findbunu varsayılan olarak yapar. Bu, gizli bir alt klasör olduğunu fark etmez ve lsbazı durumlarda varsayılan olarak gizli dosyaları rapor etmeyen avantajlı olabilir .
MrPotatoHead

6

İşte bunun için tek astarım.

 file_count=$( shopt -s nullglob ; set -- $directory_to_search_inside/* ; echo $#)

Anlamak için biraz çalışmamı aldı, ama bu güzel! Yani set -- bizim için hazırlanıyor dışında hiçbir şey yapmıyor $#ki kabuk programına geçirildi komut satırı argüman sayısı mağazalarında
xverges

@xverges Evet, "shopt -s nullglob" gizli dosyaları (.files) saymamak içindir. set - konumsal parametre sayısını (bu durumda dosya sayısı) saklamak / ayarlamak içindir. ve # $, konumsal parametre sayısını görüntülemek için (dosya sayısı).
zee

3

Özyinelemeli dizinler ile birlikte dosyaları bulmak için -R seçeneğini kullanabilirsiniz

ls -R | wc -l // to find all the files

ls -R | grep log | wc -l // to find the files which contains the word log

grep'te desenler kullanabilirsiniz


3

Önemli bir yorum

(yorum yapmak için yeterli itibar)

Bu HATA :

ls -1q some_pattern | wc -l

Eğer shopt -s nullglobayarlanacak olur, bu sayısını yazdırır TÜM normal dosyalar, desenli sadece olanları (CentOS-8 ve Cygwin üzerinde test). Diğer anlamsız hataların ne olduğunu kim bilebilir ls?

Bu DOĞRU ve çok daha hızlı:

shopt -s nullglob; files=(some_pattern); echo ${#files[@]};

Beklenen işi yapar.


Ve çalışma süreleri farklıdır.
1: 0.006CentOS ve 0.083Cygwin'de (dikkatli kullanılması durumunda). İkincisi
: 0.000CentOS'ta ve 0.003Cygwin'de.


2

Bir kabuk işlevi kullanarak böyle bir komutu kolayca tanımlayabilirsiniz. Bu yöntem herhangi bir harici program gerektirmez ve alt süreç oluşturmaz. Tehlikeli lsayrıştırma girişiminde bulunmaz ve “özel” karakterleri (beyaz boşluklar, yeni satırlar, ters eğik çizgiler vb.) İyi işler. Yalnızca kabuk tarafından sağlanan dosya adı genişletme mekanizmasına dayanır. En az sh, bash ve zsh ile uyumludur.

Aşağıdaki satır, çağrıldığı countargüman sayısını yazdıran bir işlev tanımlar .

count() { echo $#; }

İstediğiniz desenle arayın:

count log*

Globbing paterni eşleşmediğinde sonucun doğru olması için, genişletme gerçekleştiğinde kabuk seçeneği nullglob(veya failglob- zsh'de varsayılan davranış olan) ayarlanmalıdır. Bu şekilde ayarlanabilir:

shopt -s nullglob    # for sh / bash
setopt nullglob      # for zsh

Neyi saymak istediğinize bağlı olarak, kabuk seçeneğiyle de ilgilenebilirsiniz dotglob.

Ne yazık ki, en azından bash ile, bu seçenekleri yerel olarak ayarlamak kolay değildir. Bunları küresel olarak ayarlamak istemiyorsanız, en basit çözüm işlevi bu daha kıvrımlı bir şekilde kullanmaktır:

( shopt -s nullglob ; shopt -u failglob ; count log* )

Hafif sözdizimini kurtarmak istiyorsanız count log*veya gerçekten bir alt kabuk ortaya çıkmasını önlemek istiyorsanız, aşağıdaki satırlarda bir şeyleri hackleyebilirsiniz:

# sh / bash:
# the alias is expanded before the globbing pattern, so we
# can set required options before the globbing gets expanded,
# and restore them afterwards.
count() {
    eval "$_count_saved_shopts"
    unset _count_saved_shopts
    echo $#
}
alias count='
    _count_saved_shopts="$(shopt -p nullglob failglob)"
    shopt -s nullglob
    shopt -u failglob
    count'

Bir bonus olarak, bu fonksiyon daha genel bir kullanıma sahiptir. Örneğin:

count a* b*          # count files which match either a* or b*
count $(jobs -ps)    # count stopped jobs (sh / bash)

İşlevi PATH'den çağrılan bir komut dosyasına (veya eşdeğer bir C programına) dönüştürerek, findve gibi programlarla da oluşturulabilir xargs:

find "$FIND_OPTIONS" -exec count {} \+    # count results of a search

2

Bu cevaba birçok düşünce verdim, özellikle ayrıştırmayan şeyler göz önüne alındığında . İlk başta denedim

<UYARI! ÇALIŞMADI>
du --inodes --files0-from=<(find . -maxdepth 1 -type f -print0) | awk '{sum+=int($1)}END{print sum}'
</ UYARI! ÇALIŞMADI>

sadece bir dosya adı olsaydı işe yaradı.

touch $'w\nlf.aa'

ama böyle bir dosya adı yaptıysam başarısız oldum

touch $'firstline\n3 and some other\n1\n2\texciting\n86stuff.jpg'

Sonunda aşağıya koyduğum şeyi buldum. Not (herhangi bir alt dizin dahil değil) dizindeki tüm dosyaların sayısını almaya çalışıyordum. @Mat ve @Dan_Yard'ın cevaplarıyla birlikte, @mogsie tarafından belirlenen gereksinimlerin en azından çoğuna sahip olduğumu düşünüyorum (bellek hakkında emin değilim.) @Mogsie'nin cevabının doğru olduğunu düşünüyorum, ama lsçok özel bir durum olmadıkça ayrıştırmadan uzak durmaya çalışıyorum .

awk -F"\0" '{print NF-1}' < <(find . -maxdepth 1 -type f -print0) | awk '{sum+=$1}END{print sum}'

Daha okunaklı:

awk -F"\0" '{print NF-1}' < \
  <(find . -maxdepth 1 -type f -print0) | \
    awk '{sum+=$1}END{print sum}'

Bu, dosyalar için özel olarak bir bulma işlemi yapar, çıktıyı boş karakterle sınırlar (boşluklar ve satır beslemeleriyle ilgili sorunları önlemek için), ardından boş karakter sayısını sayar. Sonunda boş bir karakter olacağından, dosya sayısı boş karakter sayısından bir az olacaktır.

OP'nin sorusunu cevaplamak için dikkate alınması gereken iki durum var

1) Özyinelemesiz arama:

awk -F"\0" '{print NF-1}' < \
  <(find . -maxdepth 1 -type f -name "log*" -print0) | \
    awk '{sum+=$1}END{print sum}'

2) Özyinelemeli arama. -nameParametrenin içindekilerin biraz farklı davranışlar (gizli dosyalar vb.) İçin değiştirilmesi gerekebileceğini unutmayın .

awk -F"\0" '{print NF-1}' < \
  <(find . -type f -name "log*" -print0) | \
    awk '{sum+=$1}END{print sum}'

Eğer birisi bu cevapların bu cevapta bahsettiğim cevaplarla nasıl karşılaştırıldığını yorumlamak isterse, lütfen yapınız.


Not, bu yanıtı alırken bu düşünce sürecine girdim .


1

İşte her zaman yaptığım:

ls günlüğü * | awk 'END {yazdır NR}'


awk 'END{print NR}'ile eşdeğer olmalıdır wc -l.
musiphil

0
ls -1 log* | wc -l

Bu, satır başına bir dosya listelemek ve daha sonra satırları saymak için parametre değiştirme ile sözcük sayımı komutuna bağlamak anlamına gelir.


Ls çıkışını borularken "-1" seçeneği gerekli değildir. Ancak, hiçbir dosya desenle eşleşmiyorsa ls hata mesajını gizlemek isteyebilirsiniz. Ben önermek "ls log * 2> / dev / null | wc -l".
JohnMudd

Daniel'in cevabı altındaki tartışma burada da geçerlidir. Yeni satırlarla eşleşen dizinleriniz veya dosya adlarınız yoksa, bu iyi çalışır, ancak iyi bir yanıt en azından bu sınır koşullarını göstermeli ve büyük bir yanıtın bunlara sahip olmaması gerekir. Birçok hata, birilerinin anlamadığı kodları kopyaladığı / yapıştırdığı; bu yüzden kusurları belirtmek en azından neye dikkat edeceklerini anlamalarına yardımcı olur. (Verilen, uyarıları görmezden geldikleri ve daha sonra kodun muhtemelen amaçları için yeterince iyi olduğunu düşündükten sonra işler değiştiği için daha fazla hata meydana geldi.)
üçlü

-1

Her şeyi saymak için kelime sayım satırına ls boru:

ls | wc -l

Desenle saymak için önce grep'e boru uygulayın:

ls | grep log | wc -l
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.