Linux에서 C의 디렉토리를 재귀 적으로 나열하는 방법은 무엇입니까?
C 프로그래밍에서 모든 디렉토리와 파일을 재귀 적으로 나열해야합니다. FTW를 살펴 봤지만 사용중인 두 운영 체제 (Fedora 및 Minix)에는 포함되어 있지 않습니다. 나는 지난 몇 시간 동안 읽은 모든 다른 것들로 인해 큰 두통을 느끼기 시작했습니다.
누군가가 코드 스 니펫을 알고 있다면 그것은 놀랍거나 누군가가 나에게 좋은 방향을 줄 수 있다면 매우 감사 할 것입니다.
다음은 재귀 버전입니다.
#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;
}
왜 모든 사람들이 바퀴를 계속해서 재발 명해야한다고 주장합니까?
POSIX.1-2008 nftw()
은 단일 Unix 사양 v4 (SuSv4)에도 정의되어 있으며 Linux (glibc, man 3 nftw
), 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()
그것을보고 각 디렉토리 항목에 대한 호출 할 수 있습니다.
The only hand-wavy detail above is the decision on how many file descriptors one should let nftw()
use. If your program uses at most two extra file descriptors (in addition to the standard streams) during the file tree walk, 15 is known to be safe (on all systems having nftw()
and being mostly POSIX-compliant).
Linux에서는을 사용 sysconf(_SC_OPEN_MAX)
하여 열린 파일의 최대 수를 찾고 nftw()
호출 과 동시에 사용하는 수를 뺄 수 있지만 신경 쓰지 않을 것입니다 (유틸리티가 대부분 병리학 적으로 깊은 디렉토리 구조에서 사용된다는 것을 알지 않는 한). 15 개의 설명 자는 트리 깊이를 제한 하지 않습니다 . nftw()
속도가 느려집니다 (시스템과 C 라이브러리 구현에 따라 변경 사항을 감지하는 일반적인 기능과 장단점은 다르지만 해당 디렉토리에서 13 개보다 더 깊은 디렉토리로 이동하는 경우 디렉토리의 변경 사항을 감지하지 못할 수 있습니다). 컴파일 시간 상수를 사용하는 것만으로도 코드를 이식 할 수 있습니다. Linux뿐만 아니라 Mac OS X 및 현재의 모든 BSD 변형, 그리고 너무 오래되지 않은 대부분의 Unix 변형에서도 작동합니다.
코멘트에서 루슬란은로 전환 할 수 있다고 언급 nftw64()
들은 64 비트 크기 / 오프셋을 필요한 파일 시스템 항목을했기 때문에, 그리고 "정상적인"버전 nftw()
실패했습니다 errno == EOVERFLOW
. 올바른 해결책은 GLIBC 전용 64 비트 함수로 전환하지 않고 _LARGEFILE64_SOURCE
및 _FILE_OFFSET_BITS 64
. 이는 표준 함수 ( nftw()
, fstat()
등) 및 유형 이름 ( off_t
등) 을 사용하면서 가능한 경우 64 비트 파일 크기 및 오프셋으로 전환하도록 C 라이브러리에 지시합니다 .
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 , dirent.h . 위의 코드는 발생할 수있는 오류를 확인하지 않습니다.
ftw
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 record to process */
pos = NODE2RECORD(tmp, list);
/* prepare for next iteration */
LIST_POP(&records.list, tmp);
fwrite(pos->name, pos->len, 1, stderr);
fwrite("\n", 1, 1, stderr);
free(pos);
}
return 0;
}
편집하다
@Stargateur는 주석에서 재귀 코드가 열린 파일 제한에 도달하기 전에 스택을 오버플로 할 것이라고 언급했습니다.
스택 오버플로가 얼마나 더 나은지 알 수 없지만이 평가는 호출 될 때 프로세스가 파일 제한에 가까워지지 않는 한 정확할 것입니다.
댓글에서 @Stargateur가 언급 한 또 다른 요점은 재귀 코드의 깊이가 최대 하위 디렉터리 수 (ext4 파일 시스템의 경우 64000)에 의해 제한되고 하드 링크가 극히 적다는 것입니다 (폴더에 대한 하드 링크가 Linux / Unix에서 허용됨).
코드가 Linux (질문에 따르면)에서 실행중인 경우 이는 좋은 소식이므로이 문제는 실제 문제가 아닙니다 (macOS 또는 Windows에서 코드를 실행하지 않는 한) ... 64K 하위 폴더에도 불구하고 재귀에서 스택이 크게 열릴 수 있습니다.
그렇긴하지만 재귀 없음 옵션은 처리되는 항목의 양에 쉽게 제한을 추가 할 수있을뿐만 아니라 결과를 캐시 할 수 있다는 것과 같은 장점이 있습니다.
추신
의견에 따르면 순환 계층 구조를 확인하지 않는 비 재귀 버전의 코드가 있습니다. 폴더에 대한 하드 링크가 허용되지 않는 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 record to process */
pos = NODE2RECORD(tmp, list);
/* prepare for next iteration */
LIST_POP(&records.list, tmp);
fwrite(pos->name, pos->len, 1, stderr);
fwrite("\n", 1, 1, stderr);
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 .
ReferenceURL : https://stackoverflow.com/questions/8436841/how-to-recursively-list-directories-in-c-on-linux
'UFO ET IT' 카테고리의 다른 글
관계형 대수로 MAX를 어떻게 찾을 수 있습니까? (0) | 2021.01.05 |
---|---|
인터넷 연결 상태 변경에 대한 Android 이벤트 (0) | 2021.01.05 |
배열 변경시 $ watch가 트리거되지 않음 (0) | 2021.01.05 |
Github SSH 구성 (0) | 2021.01.05 |
popBackStack 후 ViewPager의 조각이 복원되지 않음 (0) | 2021.01.05 |