执行此操作的最低级别方法是使用ls 使用的相同 Linux 系统调用。
那么看strace -efile,getdents ls的输出:
execve("/bin/ls", ["ls"], [/* 72 vars */]) = 0
...
openat(AT_FDCWD, ".", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
getdents(3, /* 23 entries */, 32768) = 840
getdents(3, /* 0 entries */, 32768) = 0
...
getdents 是一个特定于 Linux 的系统调用。手册页说 libc's readdir(3) POSIX API function 在幕后使用它。
最低级别的可移植方式(可移植到POSIX系统),是使用libc函数打开目录并读取条目。 POSIX没有指定确切的系统调用接口,不像非目录文件。
这些功能:
DIR *opendir(const char *name);
struct dirent *readdir(DIR *dirp);
可以这样使用:
// print all directories, and symlinks to directories, in the CWD.
// like sh -c 'ls -1UF -d */' (single-column output, no sorting, append a / to dir names)
// tested and works on Linux, with / without working d_type
#define _GNU_SOURCE // includes _BSD_SOURCE for DT_UNKNOWN etc.
#include <dirent.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
DIR *dirhandle = opendir("."); // POSIX doesn't require this to be a plain file descriptor. Linux uses open(".", O_DIRECTORY); to implement this
//^Todo: error check
struct dirent *de;
while(de = readdir(dirhandle)) { // NULL means end of directory
_Bool is_dir;
#ifdef _DIRENT_HAVE_D_TYPE
if (de->d_type != DT_UNKNOWN && de->d_type != DT_LNK) {
// don't have to stat if we have d_type info, unless it's a symlink (since we stat, not lstat)
is_dir = (de->d_type == DT_DIR);
} else
#endif
{ // the only method if d_type isn't available,
// otherwise this is a fallback for FSes where the kernel leaves it DT_UNKNOWN.
struct stat stbuf;
// stat follows symlinks, lstat doesn't.
stat(de->d_name, &stbuf); // TODO: error check
is_dir = S_ISDIR(stbuf.st_mode);
}
if (is_dir) {
printf("%s/\n", de->d_name);
}
}
}
还有一个在 Linux stat(3posix) man page 中读取目录条目和打印文件信息的完全可编译示例。(不是 Linux stat(2) man page;它有一个不同的示例)。
readdir(3) 的手册页说 struct dirent 的 Linux 声明是:
struct dirent {
ino_t d_ino; /* inode number */
off_t d_off; /* not an offset; see NOTES */
unsigned short d_reclen; /* length of this record */
unsigned char d_type; /* type of file; not supported
by all filesystem types */
char d_name[256]; /* filename */
};
d_type 是DT_UNKNOWN,在这种情况下,您需要stat 来了解目录条目本身是否是目录。或者它可以是DT_DIR 或其他东西,在这种情况下,您可以确定它是不是一个目录,而不必stat 它。
一些文件系统,比如我认为的 EXT4 和最近的 XFS(带有新的元数据版本),将类型信息保存在目录中,因此无需从磁盘加载 inode 就可以返回它。这对于find -name 来说是一个巨大的加速:它不需要统计任何东西来通过子目录递归。但对于不这样做的文件系统,d_type 将始终为DT_UNKNOWN,因为填充它需要读取所有 inode(甚至可能不会从磁盘加载)。
有时你只是匹配文件名,而不需要类型信息,所以如果内核花费大量额外的 CPU 时间(尤其是 I/O 时间)来填充 d_type,那会很糟糕。不便宜。 d_type 只是一个性能捷径;你总是需要一个后备(除了可能在为嵌入式系统编写时,你知道你正在使用什么 FS 并且它总是填写 d_type,并且你有一些方法可以在将来有人试图检测损坏时在另一种 FS 类型上使用此代码。)