파일 수가 매우 많을 때 (> 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
가 더 빠릅니다 . 정렬을 중지 하고 비슷한 성능을 가진 경우 ls
ls
ls
find
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 -l
1M 파일이있는 외부 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 -l
5.3 초가 걸리고 0.5 초가 더 걸렸습니다. 그래서 저는 나무가 속도면에서 상당히 경쟁력이 있다고 생각합니다.
하위 폴더가없는 한 트리는 파일을 계산하는 빠르고 쉬운 방법입니다.
또한 재미있게도 tree | grep '^├'
현재 디렉토리의 파일 / 폴더 만 표시 할 수 있습니다 -이것은 기본적으로 훨씬 느린 버전입니다 ls
.
Brew install tail
OS 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 -l
0.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