디렉토리에 몇 개의 파일이 있는지 계산하는 가장 효율적인 방법은 무엇입니까?


55

CentOS 5.9

다른 날 디렉토리에 많은 파일이있는 문제가 발생했습니다. 그것을 계산하기 위해, 나는 달렸다ls -l /foo/foo2/ | wc -l

단일 디렉토리에 백만 개가 넘는 파일이 있다는 것이 밝혀졌습니다 (긴 이야기-근본 원인이 수정되었습니다).

내 질문은 : 더 빠른 계산 방법이 있습니까? 카운트를 얻는 가장 효율적인 방법은 무엇입니까?


5
ls -l|wc -l때문에의 첫 번째 행의 전체 블록 떨어져 하나가 될 것입니다 ls -l출력
토마스 니만

3
@ThomasNyman 점과 의사 점 항목으로 인해 실제로 여러 개가 해제 될 수 있지만 -A플래그 를 사용하면 이러한 항목을 피할 수 있습니다 . -l확장 목록 형식을 생성하기 위해 파일 메타 데이터를 읽기 때문에 문제가됩니다. NOT -l을 사용하여 강제 로 사용하는 \ls것이 훨씬 나은 옵션입니다 ( -1배관 출력시 가정). 최상의 솔루션 은 Gilles의 답변 을 참조 하십시오.
Caleb

2
@Caleb ls -l는 숨겨진 파일이나 ...항목을 출력하지 않습니다 . ls -a출력은 숨김 파일 포함 포함을 . 하고 ..있는 동안 ls -A출력은 숨겨진 파일을 포함 제외 . 하고 ... 에서 질의 답변 bash는 dotglob 쉘 옵션은 숨겨진 파일을 포함하도록 확장을 야기 제외 . 하고 ...
토마스 Nyman

답변:


61

짧은 답변:

\ls -afq | wc -l

( .과를 포함 ..하므로 2를 빼십시오.)


디렉토리에 파일을 나열하면 다음과 같은 세 가지 일반적인 상황이 발생할 수 있습니다.

  1. 디렉토리에서 파일 이름을 열거합니다. 이 방법은 피할 수 없습니다. 디렉토리에서 파일을 열거하지 않고 계산할 수있는 방법은 없습니다.
  2. 파일 이름 정렬 쉘 와일드 카드와 ls명령이 그렇게합니다.
  3. stat디렉토리인지 여부와 같이 각 디렉토리 항목에 대한 메타 데이터를 검색하기 위해 호출 합니다.

# 3은 각 파일마다 inode를로드해야하기 때문에 가장 비쌉니다. 이에 비해 # 1에 필요한 모든 파일 이름은 몇 블록에 간결하게 저장됩니다. # 2는 약간의 CPU 시간을 낭비하지만 종종 거래 차단기가 아닙니다.

파일 이름에 줄 바꿈이 없으면 ls -A | wc -l디렉토리에 몇 개의 파일이 있는지 간단하게 알려줍니다. 당신의 별칭이있는 경우 조심하십시오 ls,이에 대한 호출 트리거 할 수있다 stat(예를 ls --color또는 ls -F전화로를 필요로하는 파일 형식을 알 필요 stat), 그래서 명령 줄에서 전화 command ls -A | wc -l또는 \ls -A | wc -l별칭을 방지하기 위해.

파일 이름에 줄 바꿈이 있으면 줄 바꿈이 나열되는지 여부는 Unix 변형에 따라 다릅니다. GNU coreutils 및 BusyBox는 기본적으로 ?줄 바꿈 을 표시 하므로 안전합니다.

ls -f항목을 정렬하지 않고 나열하려면 호출 하십시오 (# 2). 이 기능은 자동으로 켜집니다 -a(적어도 최신 시스템에서는). -f옵션은 POSIX이 아니라 선택 상태입니다 대부분의 구현은 지원하지만 BusyBox는 지원하지 않습니다. 이 옵션 -q은 줄 바꿈을 포함하여 인쇄 할 수없는 문자를 ?; POSIX이지만 BusyBox에서 지원하지 않으므로 이름에 개행 문자가 포함 된 파일을 과도하게 계산하여 BusyBox 지원이 필요한 경우 생략하십시오.

디렉토리에 서브 디렉토리가없는 경우 대부분의 버전은 해당 항목을 find호출하지 않습니다 stat(리프 디렉토리 최적화 : 링크 수가 2 인 디렉토리는 서브 디렉토리를 가질 수 없으므로 서브 디렉토리 find가 없으면 항목의 메타 데이터를 찾을 필요가 없습니다. -type필요 조건 등 ). 그래서 find . | wc -l디렉토리 하위 디렉토리가 없다고과 파일 이름이 개행 문자가없는 것을 제공 디렉토리에있는 파일을 계산하는 휴대용 빠른 방법입니다.

디렉토리에 서브 디렉토리가 없지만 파일 이름에 개행이 포함될 수있는 경우 이들 중 하나를 시도하십시오 (두 번째 디렉토리는 지원되는 경우 더 빠르지 만 눈에 띄지 않을 수 있음).

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

반면에 find디렉토리에 하위 디렉토리가있는 경우 사용하지 마십시오 . 모든 항목을 find . -maxdepth 1호출 stat할 수도 있습니다 (최소한 GNU find 및 BusyBox find 사용). 정렬 (# 2)을 피하지만 성능을 저하시키는 inode 조회 (# 3)의 가격을 지불합니다.

외부 도구가없는 쉘에서을 사용하여 현재 디렉토리의 파일 수를 실행할 수 있습니다 set -- *; echo $#. 빈 파일에서 도트 파일 (이름이로 시작하는 파일)이 누락 .되고 0 대신 1이보고됩니다. 외부 프로그램을 시작할 필요가 없기 때문에 작은 디렉토리에서 파일을 계산하는 가장 빠른 방법입니다 (zsh 제외)는 정렬 단계 (# 2)로 인해 더 큰 디렉토리의 시간을 낭비합니다.

  • bash에서 이것은 현재 디렉토리의 파일을 계산하는 신뢰할 수있는 방법입니다.

    shopt -s dotglob nullglob
    a=(*)
    echo ${#a[@]}
    
  • ksh93에서 이는 현재 디렉토리의 파일을 계산하는 신뢰할 수있는 방법입니다.

    FIGNORE='@(.|..)'
    a=(~(N)*)
    echo ${#a[@]}
    
  • zsh에서 이것은 현재 디렉토리의 파일을 계산하는 신뢰할 수있는 방법입니다.

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

    mark_dirs옵션이 설정되어 있으면 반드시 끄십시오 : a=(*(DNoN^M)).

  • POSIX 셸에서 이것은 현재 디렉토리의 파일을 계산하는 신뢰할 수있는 방법입니다.

    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"
    

이러한 모든 메소드는 zsh를 제외한 파일 이름을 정렬합니다.


1
백만 개가 넘는 파일에 대한 경험적 테스트에 따르면 추가 확인이 필요한 선언 과 같은 것을 추가하지 않는 한 find -maxdepth 1쉽게 따라갈 수 있습니다 . GNU find가 실제로 호출하는 것이 확실 합니까? 속도 저하조차도 파일 세부 정보를 반환하면 늪지 수 와 비교할 수 없습니다 . 반면에 클리어 스피드 승자는 비 정렬 글로브를 사용하고 있습니다. (정렬 되지 않은 글로브는 비 정렬 글로브가 2 배 더 빠르지 만 , 2 배 더 느립니다 .) 파일 시스템 유형이 이러한 결과에 큰 영향을 미치는지 궁금합니다. \ls -U-typestatfind -typels -lzshls
Caleb

@Caleb 나는 달렸다 strace. 디렉토리에 하위 디렉토리가있는 경우에만 해당됩니다. 그렇지 않으면 find리프 디렉토리 최적화가 시작됩니다 (조차도 없음 -maxdepth 1). 파일 시스템 유형을 포함하여 많은 것들이 결과에 영향을 줄 수 있습니다 (호출 stat은 디렉토리를 트리로 나타내는 파일 시스템보다 디렉토리를 선형 목록으로 나타내는 파일 시스템에서 훨씬 비쌉니다). 디스크, 콜드 또는 핫 캐시 등
Gilles

1
역사적으로, ls -f호출을 방지하기 위해 신뢰할 수있는 방법이었다 stat이것은 종종 단순히 (그것은 또한 원인이되는) "출력이 정렬되지 않습니다"오늘 설명하고, 포함되어 있습니까 - .... -A-U표준 옵션이 아니다.
Random832

1
공통 확장명 (또는 다른 문자열)을 가진 파일을 구체적으로 세려면 명령에 파일을 삽입하면 추가 2가 제거됩니다. 다음은 예입니다.\ls -afq *[0-9].pdb | wc -l
Steven C. Howell

version sh (AT&T Research) 93u+ 2012-08-01내 데비안 기반 시스템에서 ksh93을 사용 하는 FYI FIGNORE가 작동하지 않는 것 같습니다. ...항목은 결과 배열에 포함되어 있습니다
세르지 Kolodyazhnyy

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

내 컴퓨터에서 상당히 빠르지 만 로컬 .디렉토리가 카운트에 추가됩니다.


1
감사. 그래도 바보 같은 질문을해야합니다. 왜 더 빠릅니까? 파일 속성을 조회하지 않아도됩니까?
Mike B

2
네, 이해합니다. -type매개 변수를 사용하지 않는 find것이ls
Joel Taylor

1
흠 .... find 의 문서를 잘 이해하고 있다면 이것이 실제로 내 대답보다 낫습니다. 더 많은 경험을 가진 사람은 확인할 수 있습니까?
Luis Machuca

-mindepth 1디렉토리 자체를 생략하려면 a 를 추가하십시오 .
Stéphane Chazelas

8

ls -1U파이프는 파일 항목을 정렬하지 않고 디스크의 폴더에서 정렬 될 때 파일을 읽기만하기 때문에 약간의 리소스를 소비해야합니다. 또한 출력이 적어 약간의 작업이 줄어 듭니다 wc.

당신은 또한 ls -f어느 단축키를 사용할 수 있습니다 ls -1aU.

파이프없이 명령을 통해 리소스를 효율적으로 수행 할 수있는 방법이 있는지 모르겠습니다.


8
BTW -1이 출력 파이프로 이행 한 경우 암시
enzotib

@enzotib-그래요? 와우 ... 매일 새로운 것을 배웁니다!
Luis Machuca

6

또 다른 비교 포인트. 쉘 oneliner가 아니지만이 C 프로그램은 수퍼 플로어를 수행하지 않습니다. 숨겨진 파일은 출력과 일치하도록 무시됩니다 ls|wc -l( ls -l|wc -l첫 번째 출력 행의 총 블록으로 인해 하나가 꺼져 있음).

#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를 사용하면 약간의 오버 헤드가 발생하고 기본 시스템 호출에 전달되는 버퍼 크기를 제어 할 수 없습니다 ( getdentsLinux의 경우)
Stéphane Chazelas

3

당신은 시도 할 수 있습니다 perl -e 'opendir($dh,".");$i=0;while(readdir $dh){$i++};print "$i\n";'

쉘 파이프와 타이밍을 비교하는 것이 흥미로울 것입니다.


내 시험에서이 세 가지 다른 빠른 솔루션 (로 거의 정확히 같은 속도 유지 find -maxdepth 1 | wc -l, \ls -AU | wc -l그리고 zsh기반의 비 정렬 글로브 및 배열 수를). 즉, 외부 파일 속성 정렬 또는 읽기와 같은 다양한 비 효율성으로 옵션을 능가합니다. 이미 펄에 될 일이없는 한 나는 :) 간단한 솔루션을 통해 사용하여 가치가 아니라, 어느 당신에게 아무것도 얻을하지 않기 때문에 말을 감히 것
갈렙

여기에는 개수에 디렉토리 항목 ...디렉토리 항목 이 포함 되므로 실제 파일 수 (및 하위 디렉토리)를 얻으려면 2를 빼야합니다. 현대 펄에서는 perl -E 'opendir $dh, "."; $i++ while readdir $dh; say $i - 2'그렇게 할 것입니다.
Ilmari Karonen

2

에서 이 답변 , 나는 가능한 솔루션으로이 일을 생각할 수 있습니다.

/*
 * 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);
}

위의 C 프로그램을 파일을 나열해야하는 디렉토리에 복사하십시오. 그런 다음 다음 명령을 실행하십시오.

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

1
몇 가지 : 1)이를 위해 사용자 지정 프로그램을 사용하려는 경우 파일을 세고 카운트를 인쇄하면됩니다. 2) 비교 대상 ls -f에 필터링하지 않는, d_type모든 단지에 d->d_ino != 0; 3) 2 빼기 ....
Matei David

허용되는 것보다 40 배 빠른 타이밍 예제는 링크 된 답변을 참조하십시오 ls -f.
Matei David

1

외부 프로그램이 필요하지 않지만 얼마나 효율적인지 모르는 bash 전용 솔루션 :

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

이를 위해 가장 효율적인 리소스 효율적인 방법으로 글로벌 확장이 필요하지 않습니다. 처리 할 항목 수의 상한을 갖는 대부분의 쉘 외에도 백만 개 이상의 항목을 처리 할 때 폭탄이 터질 것입니다. 정렬 옵션이없는 find 또는 ls가 포함 된 솔루션이 더 빠릅니다.
Caleb

@Caleb, 이전 버전의 ksh에만 AFAIK와 같은 제한이 있습니다 (구문을 지원하지 않음). 대부분의 다른 셸에서 제한은 사용 가능한 메모리입니다. 특히 bash에서 매우 비효율적이라는 점을 알게되었습니다.
Stéphane Chazelas

1

아마도 가장 효율적인 방법은 외부 프로세스 호출과 관련이 없을 것입니다. 그래서 나는 내기를 ...

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
상대 번호가 있습니까? 파일이 몇 개인가?
smci

0

@Joel의 답변에서 문제를 해결 한 후 .파일로 추가 했습니다.

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

tail.더 이상 계산되지 않는 첫 번째 줄만 제거하면 됩니다.


1
한 줄의 wc입력 을 생략하기 위해 한 쌍의 파이프를 추가하는 것은 입력 크기와 관련 하여 오버 헤드가 선형으로 증가하기 때문에 그리 효율적이지 않습니다 . 이 경우, 최종 카운트를 1만큼 줄 이도록 보상하기 만하면되는 이유는 다음과 같습니다.echo $(( $(find /foo/foo2 -maxdepth 1 | wc -l) - 1))
Thomas Nyman

1
다른 프로세스를 통해 많은 양의 데이터를 제공하는 대신 최종 출력에서 ​​약간의 수학을 수행하는 것이 좋습니다. let count = $(find /foo/foo2 -maxdepth 1 | wc -l) - 2
Caleb

0

파이썬에서 os.listdir ()이 당신을 위해 일할 수 있습니다. 특수한 '.'을 제외하고 디렉토리 내용의 배열을 제공합니다. 및 '..'파일. 또한 이름에 '\ n'과 같은 특수 문자가있는 abt 파일을 걱정할 필요가 없습니다.

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

다음은 위의 python 명령이 'ls -Af'명령과 비교 한 시간입니다.

~ / test $ 시간 ls -Af | wc -l
399144

실제 0m0.300s
사용자 0m0.104s
시스 0m0.240s
~ / test $ time python -c 'import os; print len ​​(os.listdir ( "."))'
399142

실제 0m0.249s
사용자 0m0.064s
시스 0m0.180s

0

ls -1 | wc -l내 마음에 즉시 온다. 순수하게 학문적 인 ls -1U것보다 빠를 지 여부 는 ls -1차이가 크지 않지만 매우 큰 디렉토리에 대해서는 차이가 있습니다.


0

카운트에서 서브 디렉토리제외 하기 위해 Gilles의 승인 된 답변에 대한 변형이 있습니다.

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

외부 $(( ))산술 확장 $( )은 첫 번째에서 두 번째 서브 쉘 의 출력을 뺍니다 $( ). 첫 번째 $( )는 정확히 Gilles '입니다. 두 번째 $( )는 대상에 "링크하는"디렉토리 수를 출력합니다. 이것은 하드 링크 수를 나열하는 열이 디렉토리의 특별한 의미로 사용하는 경우 (원하는 경우 ls -od대체) 에서 비롯됩니다 ls -ld. '링크'수가 포함 ., ..및 하위 디렉토리.

성능을 테스트하지는 않았지만 비슷한 것으로 보입니다. 대상 디렉토리의 통계와 추가 된 서브 쉘 및 파이프에 대한 오버 헤드를 추가합니다.


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.