파일 수가 매우 많을 때 (> 100,000) 특정 디렉토리에서 파일 수를 찾는 가장 좋은 방법을 찾으려고합니다.
파일이 많으면 ls | wc -l실행하는 데 시간이 오래 걸립니다. 나는 이것이 모든 파일의 이름을 반환하기 때문이라고 생각합니다. 가능한 한 적은 디스크 IO를 사용하려고합니다.
나는 쓸데없는 쉘과 Perl 스크립트를 실험했다. 어떤 아이디어?
파일 수가 매우 많을 때 (> 100,000) 특정 디렉토리에서 파일 수를 찾는 가장 좋은 방법을 찾으려고합니다.
파일이 많으면 ls | wc -l실행하는 데 시간이 오래 걸립니다. 나는 이것이 모든 파일의 이름을 반환하기 때문이라고 생각합니다. 가능한 한 적은 디스크 IO를 사용하려고합니다.
나는 쓸데없는 쉘과 Perl 스크립트를 실험했다. 어떤 아이디어?
답변:
기본적으로 ls이름이 정렬되며 이름이 많으면 시간이 걸릴 수 있습니다. 또한 모든 이름을 읽고 정렬 할 때까지 출력이 없습니다. ls -f정렬을 끄 려면이 옵션을 사용하십시오 .
ls -f | wc -l
참고이 또한 가능하게됩니다 -a, 그래서 ., ..로 시작 및 기타 파일 .계산됩니다.
ls.
stat()호출하는 것과 비교할 ls때. 따라서 더 빨리 작동 find하지 않습니다 stat().
ls -f하지 않습니다 stat(). 하지만 물론 모두의 ls및 find호출 stat()특정 옵션은 같은 사용하는 경우 ls -l나 find -mtime.
ls -fR | wc -l
가장 빠른 방법은 다음과 같은 특수 목적의 프로그램입니다.
#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 비트 리눅스에서 그것을 테스트,하지만 해야 Microsoft Windows를 포함한 모든 POSIX 호환 시스템에서 작동 합니다. 버그 리포트는 환영합니다; AIX 또는 OS / 400 등에서 작동하지 않는 경우이를 업데이트하게되어 기쁩니다.
보시다시피, 그것은 원래보다 훨씬 복잡하며 반드시 그렇게해야합니다. 코드가 매우 복잡해지기를 원하지 않는 한 (예 : 하위 디렉토리 스택 관리 및 단일 루프에서 처리) 적어도 하나의 함수가 재귀 적으로 호출되어야합니다. 파일 형식을 확인해야하므로 다른 OS, 표준 라이브러리 등의 차이가 발생하기 때문에 컴파일 할 모든 시스템에서 사용할 수있는 프로그램을 작성했습니다.
오류 검사는 거의 없으며 count함수 자체는 실제로 오류를보고하지 않습니다. 정말 실패 할 수있는 유일한 전화는 opendir와 stat(당신이 운이 아니며 시스템이있는 경우 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가 제안한 두 가지 변경 사항을 통합했습니다.
lstat대신에 사용하십시오 stat. 스캔하는 디렉토리에 심볼릭 링크 된 디렉토리가있는 경우 프로그램의 동작이 변경됩니다. 이전의 동작은 (링크 된) 서브 디렉토리가 파일 수를 전체 수에 추가 한 것입니다. 새로운 동작은 연결된 디렉토리가 단일 파일로 계산되고 그 내용은 계산되지 않는다는 것입니다.2017-06-29 편집
운이 좋으면 이것은이 답변 의 마지막 편집 일 것입니다 :)
이 코드를 GitHub 리포지토리 에 복사하여 복사 / 붙여 넣기 대신 소스를 다운로드하는 대신 코드를 좀 더 쉽게 얻을 수 있도록 만들었습니다. GitHub에서 요청합니다.
소스는 Apache License 2.0에 따라 사용 가능합니다. 패치 * 환영합니다!
gcc -o dircnt dircnt.c사용은 다음과 같습니다./dircnt some_dir
찾았 어? 예를 들면 다음과 같습니다.
find . -name "*.ext" | wc -l
find /usr/share | wc -l(~ 137,000 파일)은 ls -R /usr/share | wc -l각각의 첫 번째 실행에서 (dir 이름, dir total 및 blank 행을 포함하여 ~ 160,000 줄) 보다 약 25 % 빠르며 후속 (캐시 된) 실행을 비교할 때 적어도 두 배 빠릅니다.
find가 더 빠릅니다 . 정렬을 중지 하고 비슷한 성능을 가진 경우 lslslsfind
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의 답변을 기반으로 한 테스트.
도마
ls -l | wc -l1M 파일이있는 외부 2.5 "HDD의 폴더에서 처음 실행 하는 경우 작업을 완료하는 데 약 3 분이 소요됩니다. 두 번째는 IIRC 12 초가 소요됩니다. 또한 파일 시스템에 따라 달라질 수 있습니다. 사용하고 있었다 Btrfs.
$ time perl -e 'opendir D, "."; @files = readdir D; closedir D; print scalar(@files)."\n"' 1315029 real 0m0.580s user 0m0.302s sys 0m0.275s
요구 사항에 따라 출력을 변경할 수 있지만 다음은 숫자로 명명 된 일련의 디렉토리에있는 파일 수를 재귀 적으로 계산하고보고하기 위해 작성한 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 -1 ${dir}더 많은 공간이 없으면 제대로 작동하지 않습니다. 또한,에 의해 반환 된 이름을한다는 보장이 없다 ls에 전달 될 수있는 find바와 같이, ls인간의 소비에 대한 이스케이프 인쇄 할 수없는 문자는. ( mkdir $'oddly\nnamed\ndirectory'특히 흥미로운 테스트 사례를 원한다면). ls (1)의 출력을 구문 분석하지 말아야하는 이유를
놀랍게도, 맨손 발견은 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 초가 걸렸습니다. find -type f | wc -l5.3 초가 걸리고 0.5 초가 더 걸렸습니다. 그래서 저는 나무가 속도면에서 상당히 경쟁력이 있다고 생각합니다.
하위 폴더가없는 한 트리는 파일을 계산하는 빠르고 쉬운 방법입니다.
또한 재미있게도 tree | grep '^├'현재 디렉토리의 파일 / 폴더 만 표시 할 수 있습니다 -이것은 기본적으로 훨씬 느린 버전입니다 ls.
Brew install tailOS X 용
tail이 Mac OS X 시스템에 이미 설치되어 있어야합니다.
내가 아는 가장 빠른 리눅스 파일 수는
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
디렉토리, 파일 수 등을 출력합니다.
나는 충분한 명성 포인트가없는 여기이 작성 주석 대답에,하지만 난 할 수 있어요 내 자신의 떠나 이해가되지 않습니다 대답을. 어쨌든...
Christopher Schultz 의 답변 과 관련하여 stat 를 lstat로 변경 하고 버퍼 오버플로를 피하기 위해 경계 검사를 추가하는 것이 좋습니다 .
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를 사용하는 제안은 디렉토리에 상위 디렉토리에 대한 심볼릭 링크가 포함 된 경우 주기로 이어질 수있는 심볼릭 링크를 피하는 것입니다.
lstat좋은 제안이었고, 당신은 그것을 위해 업장을받을 자격이 있기 때문에 모딩 . 이 제안은 위에 게시 된 코드와 GitHub에 통합되었습니다.
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한 시간 이외에, 한 번도 내 시도가 전혀 없었습니다 :-/
리눅스에서 가장 빠른 방법 (질문은 리눅스로 태그 지정됨)은 직접 시스템 호출을 사용하는 것입니다. 다음은 디렉토리의 파일 만 계산하고 (디어 없음) 작은 프로그램입니다. 수백만 개의 파일을 셀 수 있으며 "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;
}
추신 : 그것은 재귀 적이 지 않지만 그것을 달성하기 위해 그것을 수정할 수 있습니다.
opendir/로 수행하는 모든 작업을 추적하지는 readdir않았지만 결국 거의 동일한 코드로 요약됩니다. 이러한 방식으로 시스템 호출을 수행하는 것도 이식성이 없으며 Linux ABI가 안정적이지 않기 때문에 한 시스템에서 컴파일 된 프로그램이 다른 시스템에서 제대로 작동하지 않을 수 있습니다 (모든 * NIX 시스템 IMO의 소스에서 컴파일하는 것이 좋습니다) ). 속도가 핵심이라면 실제로 속도를 향상시키는 경우 좋은 솔루션입니다. 프로그램을 별도로 벤치마킹하지 않았습니다.
엄청난 양의 데이터가있을 때 메모리 처리에 사용하지 않는 것이 명령을 "파이핑"하는 것보다 빠르다는 것을 깨달았습니다. 결과를 파일에 저장하고 분석 한 후
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
추출은 다음과 같습니다.
ls와 디렉토리를 나열하는 다른 모든 방법 (python os.listdir, find 포함)은 libc readdir ()에 의존합니다. 그러나 readdir ()은 한 번에 32K의 디렉토리 항목 만 읽습니다. 즉, 같은 디렉토리에 많은 파일이있는 경우 (즉, 500M의 디렉토리 항목) 모든 디렉토리 항목을 읽는 데 시간이 오래 걸립니다. 특히 느린 디스크에서. 많은 수의 파일이 포함 된 디렉토리의 경우 readdir ()에 의존하는 도구보다 더 깊이 파고 들어야합니다. libc의 헬퍼 메소드보다는 getdents () syscall을 직접 사용해야합니다.
getdents ()를 사용하여 파일을 나열하는 C 코드를 여기 에서 찾을 수 있습니다 .
디렉토리의 모든 파일을 빠르게 나열하려면 두 가지 수정이 필요합니다.
먼저 버퍼 크기를 X에서 5MB로 늘리십시오.
#define BUF_SIZE 1024*1024*5
그런 다음 inode == 0으로 항목을 건너 뛰기 위해 디렉토리의 각 파일에 대한 정보를 인쇄하는 기본 루프를 수정하십시오.
if (dp->d_ino != 0) printf(...);
내 경우에는 디렉토리의 파일 이름 만 신경 쓰므로 파일 이름 만 인쇄하도록 printf () 문을 다시 작성했습니다.
if(d->d_ino) printf("%sn ", (char *) d->d_name);
컴파일하십시오 (외부 라이브러리가 필요하지 않으므로 매우 간단합니다)
gcc listdir.c -o listdir
이제 그냥 실행
./listdir [directory with insane number of files]
readdir()실제로 느리지는 않습니다. 이 성능 향상을 위해 이식성을 버릴 가치가 있다고 생각하기 전에 확실한 그림이 필요합니다.
디렉토리의 파일 수 변경 사항을 추적하려면 다음 명령을 선호합니다.
watch -d -n 0.01 'ls | wc -l'
이 명령은 0.1 초의 새로 고침 빈도로 디렉토리에있는 파일 수를 추적하기 위해 창을 열어 둡니다.
ls | wc -l0.01 초에있는 파일의 수천 또는 수백만 폴더를 완료됩니다? 심지어 ls다른 솔루션에 비해 상당히 비효율적이다. 그리고 OP는 단지 출력 변화를보고 앉아 있지 않고 카운트를 원합니다
watch그 주석 후에 매뉴얼을 읽었으며 대부분의 PC 화면의 화면 주사율이 60Hz에 불과하기 때문에 0.01 (0.1s 아님)이 비현실적이라는 것을 알았습니다 . OP는 "많은 파일에 대한 빠른 Linux 파일 수"에 대해 물었습니다. 게시하기 전에 사용 가능한 답변을 읽지 않았습니다
파일이 가장 많은 처음 10 명의 디렉터
dir=/ ; for i in $(ls -1 ${dir} | sort -n) ; { echo "$(find ${dir}${i} \
-type f | wc -l) => $i,"; } | sort -nr | head -10