これは再帰的なバージョンです:
#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()
を標準化しました 関数は、Single Unix Specification 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()
と伝えます ディレクトリエントリごとに呼び出します。
上記の唯一の微妙な詳細は、nftw()
を許可するファイル記述子の数に関する決定です。 使用する。プログラムがファイル ツリー ウォーク中に (標準ストリームに加えて) 多くても 2 つの追加ファイル記述子を使用する場合、15 が安全であることが知られています (nftw()
を持つすべてのシステムで)。 ほとんどが POSIX に準拠しています)。
Linux では、sysconf(_SC_OPEN_MAX)
を使用できます。 開いているファイルの最大数を見つけ、同時に使用する数を nftw()
で減算します 呼び出しますが、私は気にしません (ユーティリティが主に病理学的に深いディレクトリ構造で使用されることを知っていない限り)。 15 個の記述子はしない ツリーの深さを制限します。 nftw()
遅くなるだけです (変更を検出するトレードオフと一般的な機能は、システムと C ライブラリの実装によって異なりますが、そのディレクトリから 13 よりも深いディレクトリをたどると、そのディレクトリの変更を検出できない可能性があります)。このようなコンパイル時定数を使用するだけで、コードの移植性が維持されます。これは、Linux だけでなく、Mac OS X と現在のすべての BSD バリアント、およびそれほど古くない他のほとんどの Unix バリアントでも動作するはずです。
コメントで、Ruslan は nftw64()
に切り替える必要があると述べました 64 ビット サイズ/オフセットを必要とするファイル システム エントリと、nftw()
の「通常の」バージョンがあったためです。 errno == EOVERFLOW
で失敗しました .正しい解決策は、GLIBC 固有の 64 ビット関数に切り替えるのではなく、_LARGEFILE64_SOURCE
を定義することです。 と _FILE_OFFSET_BITS 64
.これらは、標準関数 (nftw()
) を使用しながら、可能であれば 64 ビットのファイル サイズとオフセットに切り替えるように C ライブラリに指示します。 、 fstat()
など) と型名 (off_t
など)