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

CentOS 5.9

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

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

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



답변

짧은 답변:

\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를 제외한 파일 이름을 정렬합니다.


답변

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

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


답변

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

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

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


답변

또 다른 비교 포인트. 쉘 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);
}

답변

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

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


답변

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

/*
 * 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

답변

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

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