Programing

많은 수의 파일에 대한 빠른 Linux 파일 수

crosscheck 2020. 7. 21. 07:52
반응형

많은 수의 파일에 대한 빠른 Linux 파일 수


파일 수가 매우 많을 때 (> 100,000) 특정 디렉토리에서 파일 수를 찾는 가장 좋은 방법을 찾으려고합니다.

파일이 많으면 "ls | wc -l"을 수행하는 데 시간이 오래 걸립니다. 나는 이것이 모든 파일의 이름을 반환하기 때문이라고 생각합니다. 가능한 한 적은 디스크 IO를 사용하려고합니다.

나는 쓸데없는 쉘과 Perl 스크립트를 실험했다. 어떤 아이디어?


기본적으로 ls이름이 정렬되며 이름이 많으면 시간이 걸릴 수 있습니다. 또한 모든 이름을 읽고 정렬 할 때까지 출력이 없습니다. ls -f정렬을 끄 려면이 옵션을 사용하십시오 .

ls -f | wc -l

참고이 또한 가능하게됩니다 -a, 그래서 ., ..로 시작 및 기타 파일 .계산됩니다.


가장 빠른 방법은 다음과 같은 특수 목적의 프로그램입니다.

#include <stdio.h>
#include <dirent.h>

int main(int argc, char *argv[]) {
    DIR *dir;
    struct dirent *ent;
    long count = 0;

    dir = opendir(argv[1]);

    while((ent = readdir(dir)))
            ++count;

    closedir(dir);

    printf("%s contains %ld files\n", argv[1], count);

    return 0;
}

캐시와 관계없이 테스트에서 캐시 기반 데이터 왜곡을 피하기 위해 동일한 디렉토리에 대해 각각 약 50 회씩 각각 50 번 실행했으며 실제 성능은 대략 다음과 같습니다 (실제 시계 시간).

ls -1  | wc - 0:01.67
ls -f1 | wc - 0:00.14
find   | wc - 0:00.22
dircnt | wc - 0:00.04

마지막 하나 dircnt는 위의 소스에서 컴파일 된 프로그램입니다.

편집 2016-09-26

대중적인 요구로 인해이 프로그램을 재귀 적으로 작성 했으므로 하위 디렉토리에 들어가 파일과 디렉토리를 개별적으로 계속 계산합니다.

일부 사람들은 이 모든 작업을 수행 하는 방법 을 알고 싶어하기 때문에 코드에 많은 일이있어서 진행 상황을 분명히하려고합니다. 이것을 작성하여 64 비트 Linux에서 테스트했지만 Microsoft Windows를 포함한 모든 POSIX 호환 시스템에서 작동 해야 합니다. 버그 리포트는 환영합니다; AIX 또는 OS / 400 등에서 작동하지 않는 경우이를 업데이트하게되어 기쁩니다.

보시다시피, 그것은 원래보다 훨씬 복잡하며 반드시 그렇게해야합니다. 코드가 매우 복잡해지기를 원하지 않는 한 (예 : 하위 디렉토리 스택 관리 및 단일 루프에서 처리) 적어도 하나의 함수가 재귀 적으로 호출되어야합니다. 파일 형식을 확인해야하므로 다른 OS, 표준 라이브러리 등의 차이가 발생하기 때문에 컴파일 할 모든 시스템에서 사용할 수있는 프로그램을 작성했습니다.

오류 검사는 거의 없으며 count함수 자체는 실제로 오류를보고하지 않습니다. 정말 실패 할 수있는 유일한 전화는 opendirstat(당신이 운이 아니며 시스템이있는 경우 dirent파일 형식이 이미 포함되어 있습니다)를. 하위 디렉토리 경로 이름의 전체 길이를 확인하는 것에 대해 편집증이 아니지만 이론적으로 시스템은보다 긴 경로 이름을 허용해서는 안됩니다 PATH_MAX. 우려 사항이 있으면 수정할 수는 있지만 C 작성을 배우는 사람에게 설명해야 할 코드가 더 많습니다.이 프로그램은 하위 디렉토리로 재귀 적으로 다이빙하는 방법의 예입니다.

#include <stdio.h>
#include <dirent.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <sys/stat.h>

#if defined(WIN32) || defined(_WIN32) 
#define PATH_SEPARATOR '\\' 
#else
#define PATH_SEPARATOR '/' 
#endif

/* A custom structure to hold separate file and directory counts */
struct filecount {
  long dirs;
  long files;
};

/*
 * counts the number of files and directories in the specified directory.
 *
 * path - relative pathname of a directory whose files should be counted
 * counts - pointer to struct containing file/dir counts
 */
void count(char *path, struct filecount *counts) {
    DIR *dir;                /* dir structure we are reading */
    struct dirent *ent;      /* directory entry currently being processed */
    char subpath[PATH_MAX];  /* buffer for building complete subdir and file names */
    /* Some systems don't have dirent.d_type field; we'll have to use stat() instead */
#if !defined ( _DIRENT_HAVE_D_TYPE )
    struct stat statbuf;     /* buffer for stat() info */
#endif

/* fprintf(stderr, "Opening dir %s\n", path); */
    dir = opendir(path);

    /* opendir failed... file likely doesn't exist or isn't a directory */
    if(NULL == dir) {
        perror(path);
        return;
    }

    while((ent = readdir(dir))) {
      if (strlen(path) + 1 + strlen(ent->d_name) > PATH_MAX) {
          fprintf(stdout, "path too long (%ld) %s%c%s", (strlen(path) + 1 + strlen(ent->d_name)), path, PATH_SEPARATOR, ent->d_name);
          return;
      }

/* Use dirent.d_type if present, otherwise use stat() */
#if defined ( _DIRENT_HAVE_D_TYPE )
/* fprintf(stderr, "Using dirent.d_type\n"); */
      if(DT_DIR == ent->d_type) {
#else
/* fprintf(stderr, "Don't have dirent.d_type, falling back to using stat()\n"); */
      sprintf(subpath, "%s%c%s", path, PATH_SEPARATOR, ent->d_name);
      if(lstat(subpath, &statbuf)) {
          perror(subpath);
          return;
      }

      if(S_ISDIR(statbuf.st_mode)) {
#endif
          /* Skip "." and ".." directory entries... they are not "real" directories */
          if(0 == strcmp("..", ent->d_name) || 0 == strcmp(".", ent->d_name)) {
/*              fprintf(stderr, "This is %s, skipping\n", ent->d_name); */
          } else {
              sprintf(subpath, "%s%c%s", path, PATH_SEPARATOR, ent->d_name);
              counts->dirs++;
              count(subpath, counts);
          }
      } else {
          counts->files++;
      }
    }

/* fprintf(stderr, "Closing dir %s\n", path); */
    closedir(dir);
}

int main(int argc, char *argv[]) {
    struct filecount counts;
    counts.files = 0;
    counts.dirs = 0;
    count(argv[1], &counts);

    /* If we found nothing, this is probably an error which has already been printed */
    if(0 < counts.files || 0 < counts.dirs) {
        printf("%s contains %ld files and %ld directories\n", argv[1], counts.files, counts.dirs);
    }

    return 0;
}

2017-01-17 수정

@FlyingCodeMonkey가 제안한 두 가지 변경 사항을 통합했습니다.

  1. lstat대신에 사용하십시오 stat. 스캔하는 디렉토리에 심볼릭 링크 된 디렉토리가있는 경우 프로그램의 동작이 변경됩니다. 이전의 동작은 (링크 된) 서브 디렉토리가 파일 수를 전체 수에 추가 한 것입니다. 새로운 동작은 연결된 디렉토리가 단일 파일로 계산되고 그 내용은 계산되지 않는다는 것입니다.
  2. 파일 경로가 너무 길면 오류 메시지가 표시되고 프로그램이 중지됩니다.

2017-06-29 편집

운이 좋으면 이것은이 답변 마지막 편집 일 것입니다 :)

이 코드를 GitHub 리포지토리 에 복사하여 복사 / 붙여 넣기 대신 소스를 다운로드하는 대신 코드를 좀 더 쉽게 얻을 수 있도록 만들었습니다. 또한 누구나 풀을 제출하여 수정을 제안 할 수 있습니다. GitHub에서 요청합니다.

소스는 Apache License 2.0에 따라 사용 가능합니다. 패치 * 환영합니다!


  • "패치"는 저 같은 노인들이 "풀 요청"이라고 부르는 것입니다.

찾았 어? 예를 들면 다음과 같습니다.

find . -name "*.ext" | wc -l

ls와 perl은 40 000 파일에 대해 테스트했습니다. 동일한 속도입니다 (캐시를 지우려고하지는 않았지만).

[user@server logs]$ time find . | wc -l
42917

real    0m0.054s
user    0m0.018s
sys     0m0.040s
[user@server logs]$ time /bin/ls -f | wc -l
42918

real    0m0.059s
user    0m0.027s
sys     0m0.037s

그리고 perl opendir / readdir과 동시에 :

[user@server logs]$ time perl -e 'opendir D, "."; @files = readdir D; closedir D; print scalar(@files)."\n"'
42918

real    0m0.057s
user    0m0.024s
sys     0m0.033s

참고 : 나는 빈은 / LS 별칭 옵션 바이 패스 확인하기 위해 -f / 사용 할 수 조금 느리게하고 -f 파일 순서를 피하기 위해. -f가없는 ls는 ls가 -f와 함께 사용되는 경우를 제외하고는 find / perl보다 두 배 느립니다. 같은 시간 인 것 같습니다.

[user@server logs]$ time /bin/ls . | wc -l
42916

real    0m0.109s
user    0m0.070s
sys     0m0.044s

또한 모든 불필요한 정보없이 파일 시스템을 직접 요청하는 스크립트를 갖고 싶습니다.

Peter van der Heijden, glenn jackman 및 mark4o의 답변을 기반으로 한 테스트.

도마


요구 사항에 따라 출력을 변경할 수 있지만 다음은 숫자로 명명 된 일련의 디렉토리에있는 파일 수를 재귀 적으로 계산하고보고하기 위해 작성한 bash one-liner입니다.

dir=/tmp/count_these/ ; for i in $(ls -1 ${dir} | sort -n) ; { echo "$i => $(find ${dir}${i} -type f | wc -l),"; }

지정된 디렉토리의 모든 파일 (디렉토리가 아닌)을 재귀 적으로 찾고 결과를 해시와 같은 형식으로 리턴합니다. find 명령을 간단히 조정하면 어떤 종류의 파일을 더 구체적으로 계산할 수 있습니까?

다음과 같은 결과가 나타납니다.

1 => 38,
65 => 95052,
66 => 12823,
67 => 10572,
69 => 67275,
70 => 8105,
71 => 42052,
72 => 1184,

놀랍게도, 맨손 발견은 ls -f와 매우 비슷합니다.

> time ls -f my_dir | wc -l
17626

real    0m0.015s
user    0m0.011s
sys     0m0.009s

> time find my_dir -maxdepth 1 | wc -l
17625

real    0m0.014s
user    0m0.008s
sys     0m0.010s

물론, 소수점 이하 셋째 자리의 값은이 중 하나를 실행할 때마다 조금씩 이동하므로 기본적으로 동일합니다. 그러나 find실제 디렉토리 자체를 계산하기 때문에 하나의 추가 단위 리턴합니다 (이전에 언급 한 바와 같이 ls -f. 및 ..도 계수하므로 두 개의 추가 단위를 리턴 함).


완전성을 위해 이것을 추가하기 만하면됩니다. 정답은 물론 다른 사람이 이미 게시했지만 트리 프로그램으로 파일과 디렉토리의 수를 얻을 수도 있습니다.

tree | tail -n 1"763 디렉토리, 9290 파일"과 같은 마지막 행을 얻으려면 명령 실행하십시오 . 플래그로 추가 할 수있는 숨겨진 파일을 제외하고 파일과 폴더를 재귀 적으로 계산합니다 -a. 참고로, 내 컴퓨터에서 트리가 내 전체 디렉토리를 계산하는 데 4.8 초가 걸렸습니다.이 디렉토리는 24777 디렉토리, 238680 파일이었습니다. find -type f | wc -l5.3 초가 걸리고 0.5 초가 더 걸렸습니다. 그래서 저는 나무가 속도면에서 상당히 경쟁력이 있다고 생각합니다.

하위 폴더가없는 한 트리는 파일을 계산하는 빠르고 쉬운 방법입니다.

또한 재미있게도 tree | grep '^├'현재 디렉토리의 파일 / 폴더 만 표시 할 수 있습니다 -이것은 기본적으로 훨씬 느린 버전입니다 ls.


빠른 Linux 파일 수

내가 아는 가장 빠른 리눅스 파일 수는

locate -c -r '/home'

grep을 호출 할 필요 없습니다 ! 그러나 언급했듯이 새로운 데이터베이스 (크론 작업으로 매일 업데이트되거나 수동으로 업데이트)가 있어야합니다 sudo updatedb.

에서 사람의 위치

-c, --count
    Instead  of  writing  file  names on standard output, write the number of matching
    entries only.

또한 디렉토리도 파일로 계산한다는 것을 알아야합니다!


BTW : 시스템 유형의 파일 및 디렉토리에 대한 개요를 원하는 경우

locate -S

디렉토리, 파일 수 등을 출력합니다.


opendir()and readdir()in을 사용 하는 Perl것이 더 빠르면 시도해 볼 수 있습니다. 이러한 기능의 예를 보려면 여기를보십시오


이 대답은 매우 크고 중첩 된 디렉토리의 경우이 페이지의 다른 모든 것보다 빠릅니다.

https://serverfault.com/a/691372/84703

locate -r '.' | grep -c "^$PWD"


~ 10K 파일로 ~ 10K 폴더의 데이터 세트에서 파일을 계산하려고 할 때 여기에 왔습니다. 많은 접근 방식의 문제점은 100M 파일을 암시 적으로 스 태팅한다는 점입니다.

나는 christopher-schultz의 접근 방식을 자유롭게 확장 하여 args를 통해 디렉토리를 전달하는 것을 지원했습니다 (재귀 적 접근 방식은 stat도 사용합니다).

다음을 파일에 넣으십시오 dircnt_args.c.

#include <stdio.h>
#include <dirent.h>

int main(int argc, char *argv[]) {
    DIR *dir;
    struct dirent *ent;
    long count;
    long countsum = 0;
    int i;

    for(i=1; i < argc; i++) {
        dir = opendir(argv[i]);
        count = 0;
        while((ent = readdir(dir)))
            ++count;

        closedir(dir);

        printf("%s contains %ld files\n", argv[i], count);
        countsum += count;
    }
    printf("sum: %ld\n", countsum);

    return 0;
}

후에는 gcc -o dircnt_args dircnt_args.c다음과 같이 호출 할 수 있습니다.

dircnt_args /your/dirs/*

10K 폴더의 100M 파일에서 위의 작업은 매우 빠르게 완료됩니다 (처음 실행시 ~ 5 분, 캐시에서 추적 : ~ 23 초).

1 시간 이내에 완료된 유일한 다른 접근 방식은 캐시에서 약 1 분 동안의 ls였습니다 ls -f /your/dirs/* | wc -l. dir 당 몇 줄의 줄 바꿈으로 카운트가 사라졌습니다 ...

예상 find한 시간 이외에, 한 번도 내 시도가 전혀 없었습니다 :-/


나는 충분한 명성 포인트가없는 여기이 작성 주석 대답에,하지만 난 할 수 있어요 내 자신의 떠나 이해가되지 않습니다 대답을. 어쨌든...

Christopher Schultz답변 과 관련하여 statlstat로 변경 하고 버퍼 오버플로를 피하기 위해 경계 검사를 추가하는 것이 좋습니다 .

if (strlen(path) + strlen(PATH_SEPARATOR) + strlen(ent->d_name) > PATH_MAX) {
    fprintf(stdout, "path too long (%ld) %s%c%s", (strlen(path) + strlen(PATH_SEPARATOR) + strlen(ent->d_name)), path, PATH_SEPARATOR, ent->d_name);
    return;
}

lstat를 사용하는 제안은 디렉토리에 상위 디렉토리에 대한 심볼릭 링크가 포함 된 경우 주기로 이어질 수있는 심볼릭 링크를 피하는 것입니다.


리눅스에서 가장 빠른 방법 (질문은 리눅스로 태그 지정됨)은 직접 시스템 호출을 사용하는 것입니다. 다음은 디렉토리의 파일 만 계산하고 (디어 없음) 작은 프로그램입니다. 수백만 개의 파일을 셀 수 있으며 "ls -f"보다 2.5 배 빠르며 Christopher Schultz의 답변보다 1.3-1.5 배 빠릅니다.

#define _GNU_SOURCE
#include <dirent.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/syscall.h>

#define BUF_SIZE 4096

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

int countDir(char *dir) {


    int fd, nread, bpos, numFiles = 0;
    char d_type, buf[BUF_SIZE];
    struct linux_dirent *dirEntry;

    fd = open(dir, O_RDONLY | O_DIRECTORY);
    if (fd == -1) {
        puts("open directory error");
        exit(3);
    }
    while (1) {
        nread = syscall(SYS_getdents, fd, buf, BUF_SIZE);
        if (nread == -1) {
            puts("getdents error");
            exit(1);
        }
        if (nread == 0) {
            break;
        }

        for (bpos = 0; bpos < nread;) {
            dirEntry = (struct linux_dirent *) (buf + bpos);
            d_type = *(buf + bpos + dirEntry->d_reclen - 1);
            if (d_type == DT_REG) {
                // Increase counter
                numFiles++;
            }
            bpos += dirEntry->d_reclen;
        }
    }
    close(fd);

    return numFiles;
}

int main(int argc, char **argv) {

    if (argc != 2) {
        puts("Pass directory as parameter");
        return 2;
    }
    printf("Number of files in %s: %d\n", argv[1], countDir(argv[1]));
    return 0;
}

추신 : 그것은 재귀 적이 지 않지만 그것을 달성하기 위해 그것을 수정할 수 있습니다.


ls파일 이름을 정렬하는 데 더 많은 시간을 소비 -f하며 정렬을 사용하지 않으면 시간 이 절약됩니다.

ls -f | wc -l

또는 당신은 사용할 수 있습니다 find:

find . -type f | wc -l

엄청난 양의 데이터가있을 때 메모리 처리에 사용하지 않는 것이 명령을 "파이핑"하는 것보다 빠르다는 것을 깨달았습니다. 결과를 파일에 저장하고 분석 한 후

ls -1 /path/to/dir > count.txt && cat count.txt | wc -l

ls / find 대신 "getdents"를 사용해야합니다

다음은 getdents 접근 방식을 설명하는 매우 유용한 기사입니다.

http://be-n.com/spw/you-can-list-a-million-files-in-a-directory-but-not-with-ls.html

Here is the extract:

ls and practically every other method of listing a directory (including python os.listdir, find .) rely on libc readdir(). However readdir() only reads 32K of directory entries at a time, which means that if you have a lot of files in the same directory (i.e. 500M of directory entries) it is going to take an insanely long time to read all the directory entries, especially on a slow disk. For directories containing a large number of files, you'll need to dig deeper than tools that rely on readdir(). You will need to use the getdents() syscall directly, rather than helper methods from libc.

We can find the C code to list the files using getdents() from here:

There are two modifications you will need to do in order quickly list all the files in a directory.

First, increase the buffer size from X to something like 5 megabytes.

#define BUF_SIZE 1024*1024*5

Then modify the main loop where it prints out the information about each file in the directory to skip entries with inode == 0. I did this by adding

if (dp->d_ino != 0) printf(...);

In my case I also really only cared about the file names in the directory so I also rewrote the printf() statement to only print the filename.

if(d->d_ino) printf("%sn ", (char *) d->d_name);

Compile it (it doesn't need any external libraries, so it's super simple to do)

gcc listdir.c -o listdir

Now just run

./listdir [directory with insane number of files]

I prefer the following command to keep track of the changes in the number of files in a directory.

watch -d -n 0.01 'ls | wc -l'

The command will keeps a window open to keep track of the no of files that are in the directory with a refresh rate of 0.1 sec.


First 10 directores with the higest no of files.

dir=/ ; for i in $(ls -1 ${dir} | sort -n) ; { echo "$(find ${dir}${i} \
    -type f | wc -l) => $i,"; } | sort -nr | head -10

참고 URL : https://stackoverflow.com/questions/1427032/fast-linux-file-count-for-a-large-number-of-files

반응형