我正在尝试编写一个模仿Unix中ls命令输出的函数。我最初尝试使用scandir和alphasort来执行此操作,这确实打印了目录中的文件,并且确实对它们进行了排序,但由于某种原因,这个排序列表似乎与文件名的相同“排序列表”不匹配那给了。
例如,如果我有一个包含file.c,FILE.c和ls.c。
的目录ls按顺序显示它们:file.c FILE.c ls.c 但是当我使用alphasort / scandir对它进行排序时,它将它们排序为:FILE.c file.c ls.c
如何对目录中的文件进行排序,使其得到如此不同的结果?
答案 0 :(得分:6)
要模拟默认的ls -1
行为,请通过调用
setlocale(LC_ALL, "");
靠近main()
的开头,并使用
count = scandir(dir, &array, my_filter, alphasort);
其中my_filter()
是一个函数,对于以点.
开头的名称返回0,为所有其他名称返回1。 alphasort()
是POSIX函数,它使用区域设置归类顺序,与strcoll()
的顺序相同。
基本实现与
类似#define _POSIX_C_SOURCE 200809L
#define _ATFILE_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <locale.h>
#include <string.h>
#include <dirent.h>
#include <stdio.h>
#include <errno.h>
static void my_print(const char *name, const struct stat *info)
{
/* TODO: Better output; use info too, for 'ls -l' -style output? */
printf("%s\n", name);
}
static int my_filter(const struct dirent *ent)
{
/* Skip entries that begin with '.' */
if (ent->d_name[0] == '.')
return 0;
/* Include all others */
return 1;
}
static int my_ls(const char *dir)
{
struct dirent **list = NULL;
struct stat info;
DIR *dirhandle;
int size, i, fd;
size = scandir(dir, &list, my_filter, alphasort);
if (size == -1) {
const int cause = errno;
/* Is dir not a directory, but a single entry perhaps? */
if (cause == ENOTDIR && lstat(dir, &info) == 0) {
my_print(dir, &info);
return 0;
}
/* Print out the original error and fail. */
fprintf(stderr, "%s: %s.\n", dir, strerror(cause));
return -1;
}
/* We need the directory handle for fstatat(). */
dirhandle = opendir(dir);
if (!dirhandle) {
/* Print a warning, but continue. */
fprintf(stderr, "%s: %s\n", dir, strerror(errno));
fd = AT_FDCWD;
} else {
fd = dirfd(dirhandle);
}
for (i = 0; i < size; i++) {
struct dirent *ent = list[i];
/* Try to get information on ent. If fails, clear the structure. */
if (fstatat(fd, ent->d_name, &info, AT_SYMLINK_NOFOLLOW) == -1) {
/* Print a warning about it. */
fprintf(stderr, "%s: %s.\n", ent->d_name, strerror(errno));
memset(&info, 0, sizeof info);
}
/* Describe 'ent'. */
my_print(ent->d_name, &info);
}
/* Release the directory handle. */
if (dirhandle)
closedir(dirhandle);
/* Discard list. */
for (i = 0; i < size; i++)
free(list[i]);
free(list);
return 0;
}
int main(int argc, char *argv[])
{
int arg;
setlocale(LC_ALL, "");
if (argc > 1) {
for (arg = 1; arg < argc; arg++) {
if (my_ls(argv[arg])) {
return EXIT_FAILURE;
}
}
} else {
if (my_ls(".")) {
return EXIT_FAILURE;
}
}
return EXIT_SUCCESS;
}
请注意,为了您的目的,我故意使其比严格需要的更复杂,因为我不希望您只复制并粘贴代码。您可以更轻松地编译,运行和调查此程序,然后移植所需的更改 - 可能只是一行setlocale("", LC_ALL);
行! - 到您自己的程序,而不是尝试向您的老师/讲师/ TA解释为什么代码看起来像是从其他地方逐字复制的。
上述代码甚至适用于命令行(cause == ENOTDIR
部分)中指定的文件。它还使用单个函数my_print(const char *name, const struct stat *info)
来打印每个目录条目;为此,它会为每个条目调用stat
。
而不是构建目录条目的路径并调用lstat()
,my_ls()
打开一个目录句柄,并使用fstatat(descriptor, name, struct stat *, AT_SYMLINK_NOFOLLOW)
以与{{1}基本相同的方式收集信息但是lstat()
是从name
指定的目录开始的相对路径(descriptor
,如果dirfd(handle)
是开放的handle
)。
确实,为每个目录条目调用其中一个stat函数是“慢”(特别是如果你执行DIR *
样式输出)。但是,/bin/ls -1
的输出是供人食用的;并且经常通过ls
或more
传送让人类悠闲地观看它。这就是为什么我个人不认为“额外”的stat()调用(即使不是真的需要)是一个问题。我知道的大多数人类用户倾向于使用less
或(我最喜欢的)ls -l
。 (ls -laF --color=auto
表示只有标准输出是终端时才使用ANSI颜色;即auto
时。)
换句话说,既然你有isatty(fileno(stdout)) == 1
订单,我建议你修改输出类似于ls -1
(破折号,而不是破折号)。您只需修改ls -l
即可。
答案 1 :(得分:2)
以字母数字(字典)顺序。
当然,这会随着语言的变化而变化。尝试:
$ LANG=C ls -1
FILE.c
file.c
ls.c
和
$ LANG=en_US.utf8 ls -1
file.c
FILE.c
ls.c
这与the "collating order"有关。任何措施都不是一个简单的问题。