ls如何对文件名进行排序?

5
我正在尝试编写一个函数,模仿Unix中ls命令的输出。我原本尝试使用scandir和alphasort执行此操作,这确实会打印目录中的文件,并对其进行排序,但由于某种原因,这个排序列表似乎与ls给出的相同的“排序列表”不匹配。
例如,如果我有一个包含file.c、FILE.c和ls.c的目录。ls按以下顺序显示它们:file.c FILE.c ls.c,但当我使用alphasort/scandir进行排序时,它将它们排序为:FILE.c file.c ls.c。那么ls是如何对目录中的文件进行排序,以便给出如此不同的结果呢?

2
ls uses its own custom string comparison while alphasort is just an implementation of strcmp - MD XF
3
ls 命令具有基本的默认排序方式,按照 strcmp 的顺序排列,但是它可以受到 locale 的影响。如果想了解 locale,请从 http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap07.html 开始;对于字符串比较的规范,请参考 http://pubs.opengroup.org/onlinepubs/9699919799/functions/strcoll.html。 - user2404501
源代码是您最好的参考。 - kaylum
@kaylum 源代码非常庞大和复杂,几乎不可能分清哪个是哪个。 - MD XF
@WumpusQ.Wumbley 非常感谢!我会开始阅读它们。 - cl001
显示剩余3条评论
2个回答

8
为了模仿默认的“ls -1”行为,通过调用以下方法使您的程序支持区域设置:
setlocale(LC_ALL, "");

在您的main()函数开始附近使用,并且使用。
count = scandir(dir, &array, my_filter, alphasort);

其中my_filter()是一个函数,对于以点号.开头的名称返回0,对于其他名称返回1。alphasort()是一个使用本地排序顺序(与strcoll()相同顺序)的POSIX函数。

基本实现应该如下:

#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);”代码!)移植到您自己的程序中会更容易些。
上面的代码甚至可以处理在命令行上指定的文件(“cause == ENOTDIR”部分)。它还使用一个单一的函数“my_print(const char *name, const struct stat *info)”来打印每个目录条目;为了实现这一点,它确实调用了每个条目的“stat”。
与构建目录条目路径并调用“lstat()”不同,“my_ls()”打开一个目录句柄,并使用“fstatat(descriptor, name, struct stat *, AT_SYMLINK_NOFOLLOW)”以基本相同的方式收集信息,就像“lstat()”那样,但是“name”是以“descriptor”指定的目录为起点的相对路径(如果“handle”是一个开放的“DIR *”,则是“dirfd(handle)”)。
的确,为每个目录条目调用一个stat函数是“缓慢的”(特别是如果您需要“/bin/ls -1”样式的输出)。但是,“ls”的输出是为人类消费而设计的;而且很多时候通过“more”或“less”管道传递,以便让人类可以悠闲地查看它。这就是为什么我个人认为这里“额外”的stat()调用(即使不是真正需要的)并不是一个问题。我所知道的大多数人类用户倾向于使用“ls -l”或(我的最爱)“ls -laF --color=auto”。 (“auto”意味着仅当标准输出是终端时才使用ANSI颜色;即当“isatty(fileno(stdout)) == 1”时。)
换句话说,现在您已经得到了“ls -1”命令,我建议您将输出修改为类似于“ls -l”(短横线ell,而不是短横线one)。您只需修改“my_print()”即可实现。

对于完整性和内容,你获得了一颗金星(嗯,我能做的最好的就是点个赞 :))。 - David C. Rankin
你太棒了。非常感谢你在答案中超越了期望!你不知道我有多感激你的帮助。 - cl001

2
按字母数字(字典)顺序排列。
当然,这会因语言而异。请尝试:
$ LANG=C ls -1
FILE.c
file.c
ls.c

"并且:"
$ LANG=en_US.utf8 ls -1
file.c
FILE.c
ls.c

这与排序顺序有关。无论如何,这都不是一个简单的问题。

是的,它确实使用了一个(修改过的)strcoll。然而,从你提供的链接来看:“排序顺序是字典顺序”。 - user8017719

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接