Bir dizinde kaç tane dosya olduğunu saymanın en etkili yolu nedir?


55

CentOS 5.9

Geçen gün bir dizinde çok fazla dosya bulunan bir sorunla karşılaştım. Saymak için koştumls -l /foo/foo2/ | wc -l

Tek bir dizinde 1 milyondan fazla dosya bulunduğunu ortaya koyuyor (uzun hikaye - temel neden düzeltiliyor).

Sorum şu: sayımı yapmanın daha hızlı bir yolu var mı? Sayımı elde etmenin en etkili yolu ne olurdu?


5
ls -l|wc -lnedeniyle ilk satırında toplam bloklara kapalı birer olurdu ls -lçıktı
Thomas Nyman

3
@ThomasNyman Nokta ve nokta noktalarından dolayı sözde girişler nedeniyle bir kaç kişi tarafından kesilmiş olabilir, ancak -Abayrak kullanımıyla bunlardan kaçınılabilir . -lAyrıca genişletilmiş liste biçimini oluşturmak için okuma dosyası meta verileri nedeniyle sorunludur. -lKullanarak NOT zorlamak \lsçok daha iyi bir seçenektir ( -1boru çıkışında varsayılır.) Buradaki en iyi çözüm için Gilles'in cevabına bakınız .
Caleb

2
@Caleb ls -lherhangi bir gizli dosyayı .ve girdiyi vermez ... ls -açıkış, dahil olmak üzere gizli dosyaları içerir .ve çıktı ..ise ve hariçls -A gizli dosyaları içerir . In Gilles cevabı bash kabuk seçeneği gizli dosyaları dahil etmek genleşmeye neden olur hariç ve . ...dotglob ...
Thomas Nyman 11:13

Yanıtlar:


61

Kısa cevap:

\ls -afq | wc -l

(Bu içerir .ve ..böylece 2'yi çıkartın.)


Dosyaları bir dizinde listelediğinizde, üç ortak şey olabilir:

  1. Dizindeki dosya adlarının numaralandırılması. Bu kaçınılmazdır: Bir dizindeki dosyaları numaralandırmadan saymanın bir yolu yoktur.
  2. Dosya adlarını sıralama. Shell joker karakterler ve lskomut bunu yapar.
  3. Çağrı statBöyle bir dizin olup olmadığını her dizin girişi, hakkında meta verileri almak için.

# 3 en pahalısıdır çünkü her dosya için bir inode yüklenmesini gerektirir. Buna karşılık # 1 için gerekli olan tüm dosya isimleri kompakt bir şekilde birkaç blokta saklanır. # 2 biraz CPU zamanını boşa harcar ancak bu genellikle bir anlaşma bozucu değildir.

Dosya adlarında yeni satır yoksa, basit bir ls -A | wc -ldizinde kaç tane dosya olduğunu söyler. Sizin için bir takma ad varsa dikkatli olun ls, bu bir çağrı tetikleyebilir stat(ör ls --colorveya ls -Fbir çağrı gerektirir dosya türünü, bilmemiz gerekir stat), bu yüzden komut satırından, çağrı command ls -A | wc -lveya \ls -A | wc -lbir takma ad önlemek için.

Dosya adında yeni satırlar varsa, satır satırlarının listelenip listelenmemesi Unix değişkenine bağlıdır. GNU coreutils ve BusyBox varsayılan olarak ?newline görüntülemekte , böylece güvenlidirler.

ls -fSıralamaları olmadan girişleri listelemek için arayın (# 2). Bu otomatik olarak açılır -a(en azından modern sistemlerde). -fSeçenek POSIX'deki ancak isteğe bağlı statüsü ile olduğu; çoğu uygulama bunu destekler, ancak BusyBox'ı değil. Seçenek, -qyeni satırlar da dahil olmak üzere yazdırılamayan karakterleri değiştirir ?; bu POSIX'tir ancak BusyBox tarafından desteklenmez, bu nedenle, adı yeni satır karakteri içeren fazla sayılan dosyalar pahasına BusyBox desteğine ihtiyacınız varsa, bunu atlayın.

Dizinin alt dizini yoksa, sürümlerinin çoğu girişlerini findçağırmaz stat(yaprak dizini optimizasyonu: bağlantı sayısı 2 olan bir dizin alt dizinlere findsahip olamaz , bu nedenle girişlerin meta verilerini aramaya gerek yoktur. gibi koşulu -typegerektirir). Bu nedenle find . | wc -l, dizinde alt dizinlerin olmaması ve dosya adının yeni bir satır içermemesi şartıyla, bir dizindeki dosyaları saymanın taşınabilir ve hızlı bir yoludur.

Dizinde alt dizin bulunmuyorsa, ancak dosya adları yeni satırlar içeriyorsa, bunlardan birini deneyin (ikincisi destekleniyorsa daha hızlı olmalı, ancak belirgin şekilde olmayabilir).

find -print0 | tr -dc \\0 | wc -c
find -printf a | wc -c

Öte yandan, finddizinin alt dizinleri varsa kullanmayın : hatta her girişe find . -maxdepth 1çağrı statyapar (en azından GNU bulma ve BusyBox bulma ile). Sıralamadan kaçınır (# 2), ancak performansı düşüren bir inode aramasının (# 3) ücretini ödersiniz.

Dış araçları olmayan kabukta, geçerli dizindeki dosyaları ile çalıştırabilirsiniz set -- *; echo $#. Bu, nokta dosyalarını (adı başlayan dosyalar) özlüyor .ve boş bir dizinde 0 yerine 1 bildiriyor. Bu, küçük dizinlerdeki dosyaları saymanın en hızlı yoludur, çünkü harici bir program başlatmayı gerektirmez, ancak (zsh hariç) sıralama adımından (# 2) dolayı daha büyük dizinler için zaman harcar.

  • Kısacası, geçerli dizindeki dosyaları saymanın güvenilir bir yoludur:

    shopt -s dotglob nullglob
    a=(*)
    echo ${#a[@]}
  • Ksh93'te, geçerli dizindeki dosyaları saymanın güvenilir bir yoludur:

    FIGNORE='@(.|..)'
    a=(~(N)*)
    echo ${#a[@]}
  • Zsh'de, geçerli dizindeki dosyaları saymanın güvenilir bir yoludur:

    a=(*(DNoN))
    echo $#a

    Eğer varsa mark_dirsseçeneği ayarlanmış, bunu kapatmak için emin olun: a=(*(DNoN^M)).

  • Herhangi bir POSIX kabuğunda bu, geçerli dizindeki dosyaları saymanın güvenilir bir yoludur:

    total=0
    set -- *
    if [ $# -ne 1 ] || [ -e "$1" ] || [ -L "$1" ]; then total=$((total+$#)); fi
    set -- .[!.]*
    if [ $# -ne 1 ] || [ -e "$1" ] || [ -L "$1" ]; then total=$((total+$#)); fi
    set -- ..?*
    if [ $# -ne 1 ] || [ -e "$1" ] || [ -L "$1" ]; then total=$((total+$#)); fi
    echo "$total"

Bu yöntemlerin tümü, zsh dışında dosya adlarını sıralar.


1
> 1 milyon dosya üzerindeki ampirik testlerim, daha fazla kontrol yapmanız gereken bir beyanname gibi bir şey eklemediğiniz sürece find -maxdepth 1, bunlara kolayca ayak uyduğunu gösterir . GNU’nun gerçekten aramalar bulduğuna emin misin ? Yavaşlama bile dosya ayrıntılarını döndürürseniz, bataklıklarla karşılaştırıldığında hiçbir şey değildir . Öte yandan, net hız kazanan sıralama dışı küreyi kullanıyor. (sıralanan küreler, sıralama dışı olanın 2 kat daha hızlı olmasına rağmen 2 kat daha yavaş ). Dosya sistemi türlerinin bu sonuçları önemli ölçüde etkileyip etkilemediğini merak ediyorum. \ls -U-typestatfind -typels -lzshls
Caleb

@Caleb koştum strace. Bu sadece dizinin alt dizinleri varsa geçerlidir: Aksi halde, findyaprak dizini optimizasyonu devreye giriyor (olmasa bile -maxdepth 1), bundan bahsetmeliydim. Dosya sistemi türü de dahil olmak üzere bir çok şey etkileyebilir (aramalar stat, dizinleri bir arada bir araya getirilmiş olsun olmasın, dizinleri ağaç olarak temsil eden dosya sistemlerinden ziyade dizinleri temsil eden dosya sistemlerinde çok daha pahalıdır). diskte, soğuk veya sıcak önbellek, vb.
Gilles 'kötüleşti'

1
Tarihsel ls -folarak, çağrıyı engellemenin güvenilir bir yolu olmuştur stat- bu genellikle bugün “çıktı sıralanmamıştır” (ayrıca neden olur) olarak tanımlanır ve içerir .ve içerir ... -Ave -Ustandart seçenek değildir.
Random832 11:13

1
Özellikle ortak bir uzantıya (veya başka bir dizeye) sahip dosyayı saymak istiyorsanız, bunu komutun içine eklemek fazladan 2'yi ortadan kaldırır. İşte bir örnek:\ls -afq *[0-9].pdb | wc -l
Steven C. Howell

Bilginize, version sh (AT&T Research) 93u+ 2012-08-01Debian tabanlı sistemimde ksh93 varken , FIGNOREişe yaramış gibi görünmüyor. .Ve ..girişler çıkan diziye dahil edilir
Sergiy Kolodyazhnyy

17
find /foo/foo2/ -maxdepth 1 | wc -l

Makinemde oldukça hızlı, ancak yerel .dizin sayıya eklenir.


1
Teşekkürler. Yine de saçma bir soru sormaya mecburum: neden daha hızlı? Çünkü aranan dosya özniteliklerini rahatsız etmiyor mu?
Mike B,

2
Evet, bu benim anlayışım. -typeParametre kullanmadığınız findsürece daha hızlı olması gerekirls
Joel Taylor

1
Hmmm .... Eğer iyi bulmanın belgelerini anlıyorsam , bu aslında benim cevabımdan daha iyi olmalı. Daha fazla deneyime sahip olan herkes doğrulayabilir mi?
Luis Machuca 11:13

-mindepth 1Dizinin kendisini atlamak için a ekleyin .
Stéphane Chazelas,

8

ls -1UBorunun biraz daha az kaynak harcaması gerektiğinden, dosya girişlerini sıralama girişiminde bulunmadığından, diskteki klasörde sıralandıklarında bunları okur. Aynı zamanda daha az iş üretiyor, bunun için biraz daha az iş yapıyor wc.

Ayrıca ls -f, aşağı yukarı kısayol olanı da kullanabilirsiniz ls -1aU.

Olsa da, boru olmadan bir komutla bunu yapmak için kaynak verimli bir yolu olup olmadığını bilmiyorum.


8
Btw, -1 çıkışı bir boruya gittiğinde ima edilir
enzotib

@enzotib - öyle mi? Vay be ... Biri her gün yeni bir şeyler öğrenir!
Luis Machuca

6

Başka bir karşılaştırma noktası. Oneliner bir kabuk olmamakla birlikte, bu C programı gereksiz herhangi bir şey yapmaz. Gizli dosyaların çıktıyla eşleşmesi göz ardı edilir ls|wc -l( ls -l|wc -lilk çıktı satırındaki toplam bloklar nedeniyle birer birer kapalı).

#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <error.h>
#include <errno.h>

int main(int argc, char *argv[])
{
    int file_count = 0;
    DIR * dirp;
    struct dirent * entry;

    if (argc < 2)
        error(EXIT_FAILURE, 0, "missing argument");

    if(!(dirp = opendir(argv[1])))
        error(EXIT_FAILURE, errno, "could not open '%s'", argv[1]);

    while ((entry = readdir(dirp)) != NULL) {
        if (entry->d_name[0] == '.') { /* ignore hidden files */
            continue;
        }
        file_count++;
    }
    closedir(dirp);

    printf("%d\n", file_count);
}

readdir()Stdio API'sini kullanmak bazı ek getdents
yükler ekler

3

Deneyebilirsin perl -e 'opendir($dh,".");$i=0;while(readdir $dh){$i++};print "$i\n";'

Zamanlamaları kabuk borunuzla karşılaştırmak ilginç olurdu.


Benim testlerde, bu diğer üç en hızlı çözümleri (aynı hemen hemen aynı hızda tutar find -maxdepth 1 | wc -l, \ls -AU | wc -lve zshesaslı olmayan sıralama topak ve dizi sayısı). Başka bir deyişle, yabancı dosya özelliklerini sıralama veya okuma gibi çeşitli verimsiz seçenekleri yener. Sana da bir şey kazanmadığı için söylemeye teşebbüs ediyorum, zaten zaten perl'de olmadıkça daha basit bir çözüm kullanmaya değmez :)
Caleb

Bunun sayıma .ve ..dizin girişlerini içereceğini unutmayın, bu nedenle gerçek dosya sayısını (ve alt dizinleri) almak için ikisini çıkarmanız gerekir. Modern Perl'de perl -E 'opendir $dh, "."; $i++ while readdir $dh; say $i - 2'yapardı.
Ilmari Karonen 11:13

2

Gönderen Bu yanıt , muhtemel bir çözüm olarak bu bir düşünebilirsiniz.

/*
 * List directories using getdents() because ls, find and Python libraries
 * use readdir() which is slower (but uses getdents() underneath.
 *
 * Compile with 
 * ]$ gcc  getdents.c -o getdents
 */
#define _GNU_SOURCE
#include <dirent.h>     /* Defines DT_* constants */
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/syscall.h>

#define handle_error(msg) \
       do { perror(msg); exit(EXIT_FAILURE); } while (0)

struct linux_dirent {
   long           d_ino;
   off_t          d_off;
   unsigned short d_reclen;
   char           d_name[];
};

#define BUF_SIZE 1024*1024*5

int
main(int argc, char *argv[])
{
   int fd, nread;
   char buf[BUF_SIZE];
   struct linux_dirent *d;
   int bpos;
   char d_type;

   fd = open(argc > 1 ? argv[1] : ".", O_RDONLY | O_DIRECTORY);
   if (fd == -1)
       handle_error("open");

   for ( ; ; ) {
       nread = syscall(SYS_getdents, fd, buf, BUF_SIZE);
       if (nread == -1)
           handle_error("getdents");

       if (nread == 0)
           break;

       for (bpos = 0; bpos < nread;) {
           d = (struct linux_dirent *) (buf + bpos);
           d_type = *(buf + bpos + d->d_reclen - 1);
           if( d->d_ino != 0 && d_type == DT_REG ) {
              printf("%s\n", (char *)d->d_name );
           }
           bpos += d->d_reclen;
       }
   }

   exit(EXIT_SUCCESS);
}

Yukarıdaki C programını, dosyaların listelenmesi gereken dizine kopyalayın. Ardından bu komutları yürütün:

gcc getdents.c -o getdents
./getdents | wc -l

1
Birkaç şey: 1) bunun için özel bir program kullanmaya istekliysen, sadece dosyaları sayıp yazdırabilirsin; 2) karşılaştırmak ls -f, hiç filtrelememek d_type, sadece d->d_ino != 0; 3) .ve için 2'yi çıkarın ...
Matei David,

Bunun kabul edilenden 40 kat daha hızlı olduğu bir zamanlama örneği için bağlantılı cevaba bakınız ls -f.
Matei David

1

Herhangi bir harici program gerektirmeyen, ancak ne kadar verimli olduğunu bilmeyen, yalnızca bash bir çözüm:

list=(*)
echo "${#list[@]}"

Glob genişlemesi, bunu yapmanın en etkili yoludur. İşleyecekleri ürün sayısında üst sınırı olan çoğu merminin yanı sıra, bir milyon artı ürünle uğraşırken bu muhtemelen bombalayacaktır, aynı zamanda çıktıyı da sıralar. Seçeneklerini sıralama olmadan bulmak veya ls içeren çözümler daha hızlı olacaktır.
Caleb

@Caleb, sadece eski ksh sürümlerinde böyle sınırlamalar vardı (ve bu sözdizimini desteklemedi) AFAIK. Diğer bütün kabuklarda, sınır sadece mevcut hafızadır. Özellikle bash'ta çok verimsiz olacağına dair bir noktanız var.
Stéphane Chazelas,

1

Muhtemelen en verimli kaynak yolu, herhangi bir dış işlem çağrısı içermemektir. Bu yüzden üzerine bahse girerim ...

cglb() ( c=0 ; set --
    tglb() { [ -e "$2" ] || [ -L "$2" ] &&
       c=$(($c+$#-1))
    }
    for glb in '.?*' \*
    do  tglb $1 ${glb##.*} ${glb#\*}
        set -- ..
    done
    echo $c
)

1
Göreceli numaralarınız var mı? kaç dosya için
smci

0

Sorunu @Joel'in cevabından düzelttikten sonra, .dosya olarak ekledi :

find /foo/foo2 -maxdepth 1 | tail -n +2 | wc -l

tailbasitçe ilk satırı kaldırır, yani .artık sayılmaz.


1
Bir wcgiriş hattını atlamak için bir çift boru eklemek , tepegöz giriş boyutuna göre doğrusal olarak arttığından çok verimli değildir . Bu durumda, neden sadece bir defa kapalı kaldığını telafi etmek için son sayımı azaltmıyorsunuz, ki bu da sürekli bir zaman işlemidir:echo $(( $(find /foo/foo2 -maxdepth 1 | wc -l) - 1))
Thomas Nyman

1
Bu kadar veriyi başka bir işlemle beslemek yerine, nihai çıktıda biraz matematik yapmak daha iyi olur. let count = $(find /foo/foo2 -maxdepth 1 | wc -l) - 2
Caleb

0

python içinde os.listdir () işini sizin için yapabilir. Özel 'hariç' dizinin içeriğini bir dizi verir. ve '..' dosyaları. Ayrıca, adında '\ n' gibi özel karakterlere sahip abt dosyaları için endişelenmenize gerek yok.

python -c 'import os;print len(os.listdir("."))'

Aşağıdaki 'pyshon komutunun' ls -Af 'komutuyla karşılaştırıldığında geçen süredir.

~ / $ $ zaman test ls -Af | wc -l
399144

gerçek 0m0.300'ler
kullanıcı 0m0.104s
Sys 0m0.240s
~ / test $ time python -c 'içe aktarma os; yazdırma len (os.listdir ("."))'
399142

gerçek 0m0.249s
kullanıcı 0m0.064s
Sys 0m0.180'ler

0

ls -1 | wc -lhemen aklıma geliyor. Tamamen akademik ls -1Uolmaktan daha hızlı olup olmadığı ls -1- fark çok büyük dizinler için önemsiz olmalıdır.


0

Alt dizinleri sayıdan hariç tutmak için, işte kabul edilen cevapta Gilles'dan bir değişiklik var:

echo $(( $( \ls -afq target | wc -l ) - $( \ls -od target | cut -f2 -d' ') ))

Dış $(( ))aritmetik genişleme, ikinci $( )alt kabuğun çıktısını birinciden çıkarır $( ). Birincisi $( )yukarıdan tam olarak Gilles'dur. İkincisi $( ), hedefe "bağlanan" dizin sayısını gösterir. Bu, ls -od( ls -ldistenirse yerine ) gelen, sabit bağlantıların sayısını listeleyen sütunun dizinler için özel bir anlamı olduğu durumlarda gelir. "Bağlantı" sayım içerir ., ..ve herhangi bir alt dizinleri.

Performansı test etmedim, ancak benzer gözüküyordu. Hedef dizinin bir statüsünü ve eklenen alt kabuk ve boru için bazı ek yükler ekler.


-2

Echo * 'nun herhangi bir' komutundan daha verimli olacağını düşünüyorum:

echo * | wc -w

4
Ya adlarında boşluk olan dosyalar? echo 'Hello World'|wc -würetir 2.
Joseph R.

@JosephR. İhtar Emptor
Dan Garthwaite
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.