scandir(3)

โœจ NAME โœจ

scandir, scandirat, alphasort, versionsort โ€” scan a directory for matching entries ๐Ÿ—‚๏ธโœจ

So quick lil intro bestie: this whole page is about the Library Functions Manual side of scandir(3) โ€” three is the library-functions section, and yes we ARE in our library era ๐Ÿ’…๐Ÿ“š


๐Ÿ“š LIBRARY ๐Ÿ“š

Standard C library (libc, -lc) โ€” the OGs, the blueprint, no cap ๐Ÿซถ


๐Ÿซถ SYNOPSIS ๐Ÿซถ

okay so here's the shape of it, and I need you to read it exactly, because compilers do not have a sense of humour:

#include <dirent.h>

int scandir(const char *restrict dirp,
            struct dirent ***restrict namelist,
            typeof(int (const struct dirent *)) *filter,
            typeof(int (const struct dirent **, const struct dirent **))
                *compar);

int alphasort(const struct dirent **a, const struct dirent **b);
int versionsort(const struct dirent **a, const struct dirent **b);

#include <fcntl.h>          /* Definition of AT_* constants */
#include <dirent.h>

int scandirat(int dirfd, const char *restrict dirp,
            struct dirent ***restrict namelist,
            typeof(int (const struct dirent *)) *filter,
            typeof(int (const struct dirent **, const struct dirent **))
                *compar);

๐Ÿ”‘ Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

Ah yes, the part that gatekeeps your code from compiling ๐Ÿ˜ญ Here's who needs what:

scandir(), alphasort():
    /* Since glibc 2.10: */ _POSIX_C_SOURCE >= 200809L
        || /* glibc <= 2.19: */ _BSD_SOURCE || _SVID_SOURCE

versionsort():
    _GNU_SOURCE

scandirat():
    _GNU_SOURCE

Basically: _GNU_SOURCE is the golden ticket for the glibc-exclusive girlies, versionsort() and scandirat(). And scandir()/alphasort() want _POSIX_C_SOURCE >= 200809L on modern glibc, or the vintage _BSD_SOURCE / _SVID_SOURCE if you're on glibc 2.19 or older. Vintage-core fr ๐ŸŽ€


๐Ÿ’– DESCRIPTION ๐Ÿ’–

Okay so the main character: scandir() scans the directory dirp, and calls filter() on every single directory entry it sees. Entries where filter() returns nonzero get stored in strings allocated via malloc(3), then sorted using qsort(3) with the comparison function compar(), and finally collected in the array namelist which is allocated via malloc(3). Three mallocs of commitment, slay ๐Ÿซถ

And if filter is NULL? All entries are selected. Every last one. No filtering, we're accepting everyone into the group chat ๐Ÿ“ฑโœจ

alphasort() and versionsort() are your go-to compar() girls:

Both of them do their thing on the strings (*a)->d_name and (*b)->d_name โ€” that's literally the field they're comparing, no cap.

๐ŸŒˆ scandirat()

scandirat() operates in exactly the same way as scandir() โ€” same energy, same everything โ€” with the following differences described here. And the differences are all about how a relative path gets resolved ๐Ÿ‘€

  • If dirp is relative, it gets interpreted relative to the directory referred to by the file descriptor dirfd โ€” rather than relative to the current working directory of the calling process, which is what scandir() does for a relative pathname. So scandirat() is the one that lets you be like "no bestie, look over here" ๐Ÿซต
  • If dirp is relative and dirfd is the special value AT_FDCWD, then dirp is interpreted relative to the current working directory of the calling process โ€” exactly like scandir() does it. So AT_FDCWD is the "never mind, do the normal thing" button ๐Ÿ˜Œ
  • If dirp is absolute, then dirfd is ignored. Periodt. Absolute paths don't need a plus-one ๐Ÿ’…

Curious why anyone would even need scandirat()? See openat(2) for an explanation of the need for it. It's giving "there's lore here" ๐Ÿงš


๐ŸŽ€ RETURN VALUE ๐ŸŽ€

scandir() returns the number of directory entries selected. That's it, that's the number, count it ๐Ÿ‘‘

On error it returns -1, with errno set to indicate the error. Classic. ๐Ÿ’€

alphasort() and versionsort() return an integer that is less than, equal to, or greater than zero if the first argument is considered to be respectively less than, equal to, or greater than the second. Standard comparison-function choreography, they ate ๐Ÿ“


๐Ÿšจ ERRORS ๐Ÿšจ

Here's the list of ways it can go wrong, and I want you to know them all so you're never caught off guard:

  • EBADF โ€” (scandirat()) dirp is relative but dirfd is neither AT_FDCWD nor a valid file descriptor. So you handed it a fd that isn't a real one and it's not the magic AT_FDCWD either. Rude awakening ๐Ÿ˜ญ
  • ENOENT โ€” The path in dirp does not exist. It's not there. It never was. Ghosted ๐Ÿซฅ
  • ENOMEM โ€” Insufficient memory to complete the operation. Your RAM said "not today" ๐Ÿ’€
  • ENOTDIR โ€” The path in dirp is not a directory. You pointed at something that isn't a folder, bestie, that's a whole different vibe ๐ŸŒธ
  • ENOTDIR โ€” (scandirat()) dirp is relative and dirfd is a file descriptor referring to a file other than a directory. Yes, ENOTDIR shows up twice on this list. That's not a typo, that's the original, and we respect the double appearance โœจ (iconic behaviour honestly)

๐Ÿ’œ ATTRIBUTES ๐Ÿ’œ

For an explanation of the terms used in this section, see attributes(7). Here's the tea, laid out in bullet form because we don't do tables in this house:

  • scandir(), scandirat() โ€” Thread safety: MT-Safe ๐Ÿงตโœ…
  • alphasort(), versionsort() โ€” Thread safety: MT-Safe locale ๐Ÿงต๐ŸŒ

So the sort girls are locale-sensitive when it comes to threading, keep that in mind ๐Ÿ‘‘


๐Ÿ“œ STANDARDS ๐Ÿ“œ

Which standards claimed which function, alphabetically by function:

  • alphasort()
  • scandir() โ€” POSIX.1-2008.
  • versionsort()
  • scandirat() โ€” GNU.

So two of them are POSIX-certified and two of them are GNU exclusives. Know your lineages ๐ŸŽ€


๐Ÿ•ฐ๏ธ HISTORY ๐Ÿ•ฐ๏ธ

The origin stories, bestie:

  • alphasort()
  • scandir() โ€” 4.3BSD, POSIX.1-2008.
  • versionsort() โ€” glibc 2.1.
  • scandirat() โ€” glibc 2.15.

Ancient code energy. They've been doing this since before you were born fr fr ๐ŸŒธ


๐Ÿ“ NOTES ๐Ÿ“

Important lore drops, don't skip these:

  • Since glibc 2.1, alphasort() calls strcoll(3). Earlier, it used strcmp(3). So the behaviour changed at 2.1 โ€” glow-up era ๐Ÿ’…
  • Before glibc 2.10, the two arguments of alphasort() and versionsort() were typed as const void *. Then when alphasort() was standardized in POSIX.1-2008, the argument type was specified as the type-safe const struct dirent **, and glibc 2.10 changed the definition of alphasort() (and the nonstandard versionsort()) to match the standard. Type safety said "I'm the main character now" โœจ

๐ŸŒŸ EXAMPLES ๐ŸŒŸ

The program below prints a list of the files in the current directory in reverse order. It's short, it's cute, it's a whole vibe ๐Ÿ”ฅ

๐Ÿงš Program source

#define _DEFAULT_SOURCE
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>

int
main(void)
{
    struct dirent **namelist;
    int n;

    n = scandir(".", &namelist, NULL, alphasort);
    if (n == -1) {
        perror("scandir");
        exit(EXIT_FAILURE);
    }

    while (n--) {
        printf("%s\n", namelist[n]->d_name);
        free(namelist[n]);
    }
    free(namelist);

    exit(EXIT_SUCCESS);
}

Notice how it walks n downwards from the end โ€” that's where the reverse order comes from, and it also frees every namelist[n] before freeing namelist itself. Cleanup queen behaviour ๐Ÿซถโœจ


๐Ÿ‘€ SEE ALSO ๐Ÿ‘€

Go read these next, they're all in the same friend group:

closedir(3), fnmatch(3), opendir(3), readdir(3), rewinddir(3), seekdir(3), strcmp(3), strcoll(3), strverscmp(3), telldir(3)


and that's the whole page bestie!! ๐Ÿ’– this was Linux man-pages 6.20, dated 2026-02-08, and it's literally the scandir(3) page. go forth and scan some directories, you absolute icon ๐Ÿ‘‘๐ŸŒธ stay safe, free your mallocs, and never forget that ENOTDIR is listed twice ๐ŸŽ€โœจ

๐Ÿ“ฆ where this page comes from
  • arch rolling: man-pages 6.20-1 ยท BSD-2-Clause AND BSD-3-Clause AND BSD-4.3TAHOE AND BSD-4-Clause-UC AND GPL-1.0-or-later AND GPL-2.0-only AND GPL-2.0-or-later AND GPL-3.0-or-later AND LGPL-3.0-linking-exception AND LGPL-3.0-or-later AND LicenseRef-Public-Domain AND LicenseRef-UltraPermissive AND Linux-man-pages-1-para AND Linux-man-pages-copyleft AND Linux-man-pages-copyleft-2-para AND Linux-man-pages-copyleft-var AND MIT AND Spencer-94
  • nixpkgs unstable: man-pages 6.20
  • ๐Ÿ“œ the page's license: Linux-man-pages-copyleft
  • ๐Ÿ’– girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms