sourcecode

Linux에서 C에 디렉토리를 반복적으로 나열하는 방법

copyscript 2022. 8. 31. 22:46
반응형

Linux에서 C에 디렉토리를 반복적으로 나열하는 방법

C 프로그래밍에 있는 모든 디렉토리와 파일을 반복적으로 나열해야 합니다.FTW에 대해 알아보고 있습니다만, 사용하고 있는 2개의 operating system(Fedora와 Minix)에는 포함되어 있지 않습니다.지난 몇 시간 동안 읽었던 여러 가지 책들 때문에 머리가 아프기 시작했어요.

코드 스니펫을 아시는 분이 계시면 정말 좋을 것 같습니다.아니면 누군가 좋은 지시를 해주시면 감사하겠습니다.

왜 다들 계속해서 바퀴를 재창조해야 한다고 주장하는 거죠?

POSIX.1-2008은 이 기능을 표준화하였습니다.이 기능은 Single Unix Specification v4(SuSv4)에도 정의되어 있으며 Linux(glibc,), OS X 및 최신 BSD 버전에서도 사용할 수 있습니다.전혀 새롭지 않아요.

순진한opendir()/readdir()/closedir()트리 트래버설 중에 디렉토리 또는 파일이 이동, 이름 변경 또는 삭제되는 경우는 거의 처리하지 않습니다.nftw()품위 있게 다뤄야 합니다.

예를 들어 현재 작업 디렉토리에서 시작하는 디렉토리 트리, 명령줄에서 명명된 각 디렉토리 또는 명령줄에서 명명된 파일만 나열하는 다음 C 프로그램을 생각해 보십시오.

/* We want POSIX.1-2008 + XSI, i.e. SuSv4, features */
#define _XOPEN_SOURCE 700

/* Added on 2017-06-25:
   If the C library can support 64-bit file sizes
   and offsets, using the standard names,
   these defines tell the C library to do so. */
#define _LARGEFILE64_SOURCE
#define _FILE_OFFSET_BITS 64 

#include <stdlib.h>
#include <unistd.h>
#include <ftw.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

/* POSIX.1 says each process has at least 20 file descriptors.
 * Three of those belong to the standard streams.
 * Here, we use a conservative estimate of 15 available;
 * assuming we use at most two for other uses in this program,
 * we should never run into any problems.
 * Most trees are shallower than that, so it is efficient.
 * Deeper trees are traversed fine, just a bit slower.
 * (Linux allows typically hundreds to thousands of open files,
 *  so you'll probably never see any issues even if you used
 *  a much higher value, say a couple of hundred, but
 *  15 is a safe, reasonable value.)
*/
#ifndef USE_FDS
#define USE_FDS 15
#endif

int print_entry(const char *filepath, const struct stat *info,
                const int typeflag, struct FTW *pathinfo)
{
    /* const char *const filename = filepath + pathinfo->base; */
    const double bytes = (double)info->st_size; /* Not exact if large! */
    struct tm mtime;

    localtime_r(&(info->st_mtime), &mtime);

    printf("%04d-%02d-%02d %02d:%02d:%02d",
           mtime.tm_year+1900, mtime.tm_mon+1, mtime.tm_mday,
           mtime.tm_hour, mtime.tm_min, mtime.tm_sec);

    if (bytes >= 1099511627776.0)
        printf(" %9.3f TiB", bytes / 1099511627776.0);
    else
    if (bytes >= 1073741824.0)
        printf(" %9.3f GiB", bytes / 1073741824.0);
    else
    if (bytes >= 1048576.0)
        printf(" %9.3f MiB", bytes / 1048576.0);
    else
    if (bytes >= 1024.0)
        printf(" %9.3f KiB", bytes / 1024.0);
    else
        printf(" %9.0f B  ", bytes);

    if (typeflag == FTW_SL) {
        char   *target;
        size_t  maxlen = 1023;
        ssize_t len;

        while (1) {

            target = malloc(maxlen + 1);
            if (target == NULL)
                return ENOMEM;

            len = readlink(filepath, target, maxlen);
            if (len == (ssize_t)-1) {
                const int saved_errno = errno;
                free(target);
                return saved_errno;
            }
            if (len >= (ssize_t)maxlen) {
                free(target);
                maxlen += 1024;
                continue;
            }

            target[len] = '\0';
            break;
        }

        printf(" %s -> %s\n", filepath, target);
        free(target);

    } else
    if (typeflag == FTW_SLN)
        printf(" %s (dangling symlink)\n", filepath);
    else
    if (typeflag == FTW_F)
        printf(" %s\n", filepath);
    else
    if (typeflag == FTW_D || typeflag == FTW_DP)
        printf(" %s/\n", filepath);
    else
    if (typeflag == FTW_DNR)
        printf(" %s/ (unreadable)\n", filepath);
    else
        printf(" %s (unknown)\n", filepath);

    return 0;
}


int print_directory_tree(const char *const dirpath)
{
    int result;

    /* Invalid directory path? */
    if (dirpath == NULL || *dirpath == '\0')
        return errno = EINVAL;

    result = nftw(dirpath, print_entry, USE_FDS, FTW_PHYS);
    if (result >= 0)
        errno = result;

    return errno;
}

int main(int argc, char *argv[])
{
    int arg;

    if (argc < 2) {

        if (print_directory_tree(".")) {
            fprintf(stderr, "%s.\n", strerror(errno));
            return EXIT_FAILURE;
        }

    } else {

        for (arg = 1; arg < argc; arg++) {
            if (print_directory_tree(argv[arg])) {
                fprintf(stderr, "%s.\n", strerror(errno));
                return EXIT_FAILURE;
            }
        }

    }

    return EXIT_SUCCESS;
}

위의 코드는 대부분 에 있습니다.print_entry()각 디렉토리 엔트리를 인쇄하는 것이 업무입니다.print_directory_tree(), 우리는 말한다.nftw()각 디렉토리 엔트리에 대해 호출합니다.

위의 유일한 손쉬운 세부 사항은 파일 기술자 수를 결정하는 것입니다.nftw()사용. 만약 당신의 프로그램이 파일 트리 워크 중에 (표준 스트림 외에) 최대 2개의 추가 파일 기술자를 사용한다면, 15는 안전하다고 알려져 있습니다.nftw()POSIX에 준거하고 있습니다).

Linux 에서는,sysconf(_SC_OPEN_MAX)열려 있는 파일의 최대 수를 찾아 동시에 사용하는 수를 빼려면nftw()(유틸리티가 주로 병리적으로 깊은 디렉토리 구조에서 사용되는지 몰랐다면) 호출은 필요 없습니다.15개의 디스크립터는 트리 깊이를 제한하지 않습니다.nftw()다만, 속도가 느려집니다(디렉토리로부터 13개 이상의 디렉토리를 이동하면, 디렉토리내의 변경을 검출할 수 없습니다만, 시스템 및 C라이브러리 실장 마다 트레이드오프와 일반적인 변경 검출 기능이 다릅니다).이러한 컴파일 시간 상수를 사용하는 것만으로, Linux 뿐만이 아니라, Mac OS X나 현재의 모든 BSD 버전, 그리고 그 외의 그다지 오래되지 않은 Unix 버전에서도 동작할 수 있습니다.

코멘트에서, 루슬란은 그들이 다른 곳으로 옮겨야 한다고 언급했다.nftw64()패킷을 로 하고, 64비트/패킷의 「 「normal」이 필요했기 입니다.nftw()가 발생했습니다.errno == EOVERFLOW은 GLIBC 함수로 GLIBC의 64비트 를 정의하는 것입니다_LARGEFILE64_SOURCE ★★★★★★★★★★★★★★★★★」_FILE_OFFSET_BITS 64는 표준 C」64)를 사용하면서 및에 지시합니다.nftw(),fstat() cetera)및et cetera)off_t을 사용하다

다음은 재귀 버전입니다.

#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <string.h>

void listdir(const char *name, int indent)
{
    DIR *dir;
    struct dirent *entry;

    if (!(dir = opendir(name)))
        return;

    while ((entry = readdir(dir)) != NULL) {
        if (entry->d_type == DT_DIR) {
            char path[1024];
            if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
                continue;
            snprintf(path, sizeof(path), "%s/%s", name, entry->d_name);
            printf("%*s[%s]\n", indent, "", entry->d_name);
            listdir(path, indent + 2);
        } else {
            printf("%*s- %s\n", indent, "", entry->d_name);
        }
    }
    closedir(dir);
}

int main(void) {
    listdir(".", 0);
    return 0;
}
int is_directory_we_want_to_list(const char *parent, char *name) {
  struct stat st_buf;
  if (!strcmp(".", name) || !strcmp("..", name))
    return 0;
  char *path = alloca(strlen(name) + strlen(parent) + 2);
  sprintf(path, "%s/%s", parent, name);
  stat(path, &st_buf);
  return S_ISDIR(st_buf.st_mode);
}

int list(const char *name) {
  DIR *dir = opendir(name);
  struct dirent *ent;
  while (ent = readdir(dir)) {
    char *entry_name = ent->d_name;
    printf("%s\n", entry_name);
    if (is_directory_we_want_to_list(name, entry_name)) {
      // You can consider using alloca instead.
      char *next = malloc(strlen(name) + strlen(entry_name) + 2);
      sprintf(next, "%s/%s", name, entry_name);
      list(next);
      free(next);
    }
  }
  closedir(dir);
}

stat.h, derent.h같은 컨텍스트에서 스킴할 가치가 있는 헤더 파일.위의 코드는 발생할 수 있는 오류를 체크하고 있지 않다는 점에 유의하십시오.

ftw.h에서 정의된 접근방식은 완전히 다릅니다.

코멘트에서도 언급했듯이, 이 태스크에 두 가지 본질적인 결함이 있는 재귀적 접근법이 있다고 생각합니다.

첫 번째 결함은 열린 파일의 제한입니다.이 제한은 딥 트래버설에 제한을 가합니다.서브폴더가 충분할 경우 재귀적 접근법이 중단됩니다.(스택 오버플로에 관한 편집 참조)

두 번째 결점은 조금 더 미묘합니다.재귀적 접근법에서는 하드링크 테스트가 매우 어렵습니다.폴더 트리가 (하드링크로 인해) 주기적인 경우 재귀적 접근 방식이 중단됩니다(스택 오버플로가 없는 것이 바람직합니다).(하드링크 관련 편집 참조)

그러나 재귀를 단일 파일 기술자 및 링크 목록으로 대체하면 이러한 문제를 쉽게 방지할 수 있습니다.

이건 학교 프로젝트가 아니고 재귀는 선택 사항인 것 같아요.

여기 응용 프로그램의 예가 있습니다.

a.out ./폴더 트리를 표시합니다.

매크로나 뭐 그런 거에 대해서 사과드립니다.저는 주로 인라인 기능을 사용하지만, 모두 하나의 기능이라면 코드를 따르기 쉽다고 생각했습니다.

#include <dirent.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>

int main(int argc, char const *argv[]) {
  /* print use instruction unless a folder name was given */
  if (argc < 2)
    fprintf(stderr,
            "\nuse:\n"
            "    %s <directory>\n"
            "for example:\n"
            "    %s ./\n\n",
            argv[0], argv[0]),
        exit(0);

  /*************** a small linked list macro implementation ***************/

  typedef struct list_s {
    struct list_s *next;
    struct list_s *prev;
  } list_s;

#define LIST_INIT(name)                                                        \
  { .next = &name, .prev = &name }

#define LIST_PUSH(dest, node)                                                  \
  do {                                                                         \
    (node)->next = (dest)->next;                                               \
    (node)->prev = (dest);                                                     \
    (node)->next->prev = (node);                                               \
    (dest)->next = (node);                                                     \
  } while (0);

#define LIST_POP(list, var)                                                    \
  if ((list)->next == (list)) {                                                \
    var = NULL;                                                                \
  } else {                                                                     \
    var = (list)->next;                                                        \
    (list)->next = var->next;                                                  \
    var->next->prev = var->prev;                                               \
  }

  /*************** a record (file / folder) item type ***************/

  typedef struct record_s {
    /* this is a flat processing queue. */
    list_s queue;
    /* this will list all queued and processed folders (cyclic protection) */
    list_s folders;
    /* this will list all the completed items (siblings and such) */
    list_s list;
    /* unique ID */
    ino_t ino;
    /* name length */
    size_t len;
    /* name string */
    char name[];
  } record_s;

/* take a list_s pointer and convert it to the record_s pointer */
#define NODE2RECORD(node, list_name)                                           \
  ((record_s *)(((uintptr_t)(node)) -                                          \
                ((uintptr_t) & ((record_s *)0)->list_name)))

/* initializes a new record */
#define RECORD_INIT(name)                                                      \
  (record_s){.queue = LIST_INIT((name).queue),                                 \
             .folders = LIST_INIT((name).folders),                             \
             .list = LIST_INIT((name).list)}

  /*************** the actual code ***************/

  record_s records = RECORD_INIT(records);
  record_s *pos, *item;
  list_s *tmp;
  DIR *dir;
  struct dirent *entry;

  /* initialize the root folder record and add it to the queue */
  pos = malloc(sizeof(*pos) + strlen(argv[1]) + 2);
  *pos = RECORD_INIT(*pos);
  pos->len = strlen(argv[1]);
  memcpy(pos->name, argv[1], pos->len);
  if (pos->name[pos->len - 1] != '/')
    pos->name[pos->len++] = '/';
  pos->name[pos->len] = 0;
  /* push to queue, but also push to list (first item processed) */
  LIST_PUSH(&records.queue, &pos->queue);
  LIST_PUSH(&records.list, &pos->list);

  /* as long as the queue has items to be processed, do so */
  while (records.queue.next != &records.queue) {
    /* pop queued item */
    LIST_POP(&records.queue, tmp);
    /* collect record to process */
    pos = NODE2RECORD(tmp, queue);
    /* add record to the processed folder list */
    LIST_PUSH(&records.folders, &pos->folders);

    /* process the folder and add all folder data to current list */
    dir = opendir(pos->name);
    if (!dir)
      continue;

    while ((entry = readdir(dir)) != NULL) {

      /* create new item, copying it's path data and unique ID */
      item = malloc(sizeof(*item) + pos->len + entry->d_namlen + 2);
      *item = RECORD_INIT(*item);
      item->len = pos->len + entry->d_namlen;
      memcpy(item->name, pos->name, pos->len);
      memcpy(item->name + pos->len, entry->d_name, entry->d_namlen);
      item->name[item->len] = 0;
      item->ino = entry->d_ino;
      /* add item to the list, right after the `pos` item */
      LIST_PUSH(&pos->list, &item->list);

      /* unless it's a folder, we're done. */
      if (entry->d_type != DT_DIR)
        continue;

      /* test for '.' and '..' */
      if (entry->d_name[0] == '.' &&
          (entry->d_name[1] == 0 ||
           (entry->d_name[1] == '.' && entry->d_name[2] == 0)))
        continue;

      /* add folder marker */
      item->name[item->len++] = '/';
      item->name[item->len] = 0;

      /* test for cyclic processing */
      list_s *t = records.folders.next;
      while (t != &records.folders) {
        if (NODE2RECORD(t, folders)->ino == item->ino) {
          /* we already processed this folder! */
          break; /* this breaks from the small loop... */
        }
        t = t->next;
      }
      if (t != &records.folders)
        continue; /* if we broke from the small loop, entry is done */

      /* item is a new folder, add to queue */
      LIST_PUSH(&records.queue, &item->queue);
    }
    closedir(dir);
  }

  /*************** Printing the results and cleaning up ***************/
  while (records.list.next != &records.list) {
    /* pop list item */
    LIST_POP(&records.list, tmp);
    /* collect and process record */
    pos = NODE2RECORD(tmp, list);
    fwrite(pos->name, pos->len, 1, stderr);
    fwrite("\n", 1, 1, stderr);
    /* free node */
    free(pos);
  }
  return 0;
}

EDIT

@Stargateur는 열린 파일 제한에 도달하기 전에 재귀 코드가 스택에 오버플로우 될 수 있다고 코멘트에서 언급했습니다.

스택 오버플로우가 어떻게 더 나은지 알 수 없지만 프로세스가 호출되었을 때 파일 제한에 근접하지 않는 한 이 평가는 정확할 수 있습니다.

@Stargateur가 코멘트에서 언급한 또 다른 포인트는 재귀 코드의 깊이는 서브 디렉토리의 최대 수(ext4 파일 시스템에서는 64000)에 의해 제한되며 하드 링크는 거의 발생하지 않는다는 것입니다(Linux/Unix에서는 폴더에 대한 하드 링크가 허용되지 않기 때문에).

만약 코드가 Linux에서 실행되고 있다면 이것은 좋은 소식입니다(질문에 따르면). 따라서 이 문제는 큰 문제가 되지 않습니다(macOS 또는 Windows에서 코드를 실행하지 않는 한).그러나 64K 하위 폴더는 스택을 완전히 열 수 있습니다.

그러나 non recursive 옵션은 처리되는 항목의 양에 대한 제한을 쉽게 추가할 수 있을 뿐만 아니라 결과를 캐시할 수 있다는 장점이 있습니다.

추신.

코멘트에 의하면, 주기적인 계층 구조를 체크하지 않는 코드의 비재귀 버전이 여기 있습니다.폴더에 대한 하드 링크가 허용되지 않는 Linux 머신에서 사용할 수 있을 만큼 빠르고 안전합니다.

#include <dirent.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>

int main(int argc, char const *argv[]) {
  /* print use instruction unless a folder name was given */
  if (argc < 2)
    fprintf(stderr,
            "\nuse:\n"
            "    %s <directory>\n"
            "for example:\n"
            "    %s ./\n\n",
            argv[0], argv[0]),
        exit(0);

  /*************** a small linked list macro implementation ***************/

  typedef struct list_s {
    struct list_s *next;
    struct list_s *prev;
  } list_s;

#define LIST_INIT(name)                                                        \
  { .next = &name, .prev = &name }

#define LIST_PUSH(dest, node)                                                  \
  do {                                                                         \
    (node)->next = (dest)->next;                                               \
    (node)->prev = (dest);                                                     \
    (node)->next->prev = (node);                                               \
    (dest)->next = (node);                                                     \
  } while (0);

#define LIST_POP(list, var)                                                    \
  if ((list)->next == (list)) {                                                \
    var = NULL;                                                                \
  } else {                                                                     \
    var = (list)->next;                                                        \
    (list)->next = var->next;                                                  \
    var->next->prev = var->prev;                                               \
  }

  /*************** a record (file / folder) item type ***************/

  typedef struct record_s {
    /* this is a flat processing queue. */
    list_s queue;
    /* this will list all the completed items (siblings and such) */
    list_s list;
    /* unique ID */
    ino_t ino;
    /* name length */
    size_t len;
    /* name string */
    char name[];
  } record_s;

/* take a list_s pointer and convert it to the record_s pointer */
#define NODE2RECORD(node, list_name)                                           \
  ((record_s *)(((uintptr_t)(node)) -                                          \
                ((uintptr_t) & ((record_s *)0)->list_name)))

/* initializes a new record */
#define RECORD_INIT(name)                                                      \
  (record_s){.queue = LIST_INIT((name).queue), .list = LIST_INIT((name).list)}

  /*************** the actual code ***************/

  record_s records = RECORD_INIT(records);
  record_s *pos, *item;
  list_s *tmp;
  DIR *dir;
  struct dirent *entry;

  /* initialize the root folder record and add it to the queue */
  pos = malloc(sizeof(*pos) + strlen(argv[1]) + 2);
  *pos = RECORD_INIT(*pos);
  pos->len = strlen(argv[1]);
  memcpy(pos->name, argv[1], pos->len);
  if (pos->name[pos->len - 1] != '/')
    pos->name[pos->len++] = '/';
  pos->name[pos->len] = 0;
  /* push to queue, but also push to list (first item processed) */
  LIST_PUSH(&records.queue, &pos->queue);
  LIST_PUSH(&records.list, &pos->list);

  /* as long as the queue has items to be processed, do so */
  while (records.queue.next != &records.queue) {
    /* pop queued item */
    LIST_POP(&records.queue, tmp);
    /* collect record to process */
    pos = NODE2RECORD(tmp, queue);

    /* process the folder and add all folder data to current list */
    dir = opendir(pos->name);
    if (!dir)
      continue;

    while ((entry = readdir(dir)) != NULL) {

      /* create new item, copying it's path data and unique ID */
      item = malloc(sizeof(*item) + pos->len + entry->d_namlen + 2);
      *item = RECORD_INIT(*item);
      item->len = pos->len + entry->d_namlen;
      memcpy(item->name, pos->name, pos->len);
      memcpy(item->name + pos->len, entry->d_name, entry->d_namlen);
      item->name[item->len] = 0;
      item->ino = entry->d_ino;
      /* add item to the list, right after the `pos` item */
      LIST_PUSH(&pos->list, &item->list);

      /* unless it's a folder, we're done. */
      if (entry->d_type != DT_DIR)
        continue;

      /* test for '.' and '..' */
      if (entry->d_name[0] == '.' &&
          (entry->d_name[1] == 0 ||
           (entry->d_name[1] == '.' && entry->d_name[2] == 0)))
        continue;

      /* add folder marker */
      item->name[item->len++] = '/';
      item->name[item->len] = 0;

      /* item is a new folder, add to queue */
      LIST_PUSH(&records.queue, &item->queue);
    }
    closedir(dir);
  }

  /*************** Printing the results and cleaning up ***************/
  while (records.list.next != &records.list) {
    /* pop list item */
    LIST_POP(&records.list, tmp);
    /* collect and process record */
    pos = NODE2RECORD(tmp, list);
    fwrite(pos->name, pos->len, 1, stderr);
    fwrite("\n", 1, 1, stderr);
    /* free node */
    free(pos);
  }
  return 0;
}

다음은 재귀적이지만 훨씬 적은 스택 공간을 사용하는 단순화된 버전입니다.

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <dirent.h>

void listdir(char *path, size_t size) {
    DIR *dir;
    struct dirent *entry;
    size_t len = strlen(path);

    if (!(dir = opendir(path))) {
        fprintf(stderr, "path not found: %s: %s\n",
                path, strerror(errno));
        return;
    }

    puts(path);
    while ((entry = readdir(dir)) != NULL) {
        char *name = entry->d_name;
        if (entry->d_type == DT_DIR) {
            if (!strcmp(name, ".") || !strcmp(name, ".."))
                continue;
            if (len + strlen(name) + 2 > size) {
                fprintf(stderr, "path too long: %s/%s\n", path, name);
            } else {
                path[len] = '/';
                strcpy(path + len + 1, name);
                listdir(path, size);
                path[len] = '\0';
            }
        } else {
            printf("%s/%s\n", path, name);
        }
    }
    closedir(dir);
}

int main(void) {
    char path[1024] = ".";
    listdir(path, sizeof path);
    return 0;
}

시스템에서 출력은 의 출력과 완전히 동일합니다.find .

언급URL : https://stackoverflow.com/questions/8436841/how-to-recursively-list-directories-in-c-on-linux

반응형