3 ms·
Can you call readdir() from a shell easily? WRT format, I'd prefer csv.
by emmelaich 2y ago
Can you call readdir() from a shell easily?
WRT format, I'd prefer csv.
- db48x 2y agoCertainly. Just do `for f in *`. See how easy that is?
- zokier 2y agohere is trivial program to dump dents to stdout, suitable for shell pipelines. example usage `./getdents64 . | xargs -0 printf "%q\n"` #define _GNU_SOURCE #include <dirent.h> #include <fcntl.h> #include <malloc.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define BUF_SIZE 32768 struct linux_dirent64 { ino64_t d_ino; /* 64-bit inode number */ off64_t d_off; /* Not an offset; see getdents() */ unsigned short d_reclen; /* Size of this dirent */ unsigned char d_type; /* File type */ char d_name[]; /* Filename (null-terminated) */ }; int writeall(char *buf, size_t len) { ssize_t wres = 0; wres = write(1, buf, len); if (wres == -1) { perror("write"); return -1; } if (((size_t)wres) < len) { return writeall(buf + wres, len - wres); } return 0; } int main(int argc, char **argv) { if (argc != 2) { return EXIT_FAILURE; } int fd = open(argv[1], O_DIRECTORY | O_RDONLY); if (fd == -1) { perror("open"); return EXIT_FAILURE; } void *buf = malloc(BUF_SIZE); ssize_t res = 0; do { res = getdents64(fd, buf, BUF_SIZE); if (res == -1) { perror("getdents64"); return EXIT_FAILURE; } void *it = buf; while (it < (buf + res)) { struct linux_dirent64 *elem = it; it += elem->d_reclen; size_t len = strlen(elem->d_name); if (writeall(elem->d_name, len + 1) == -1) { return EXIT_FAILURE; } } } while (res > 0); return EXIT_SUCCESS; }
- db48x 2y agoYou’re still doing unnecessary work. You’re turning a list of files into a string, then parsing the string back into words. Your shell already provides a nice abstraction over calling readdir directly. A glob gives you a list, with no intermediate stage as a string that needs to be parsed. You can iterate directly over that list. Every language provides either direct access to the C library, so that you can call readdir, or it provides some abstraction over it to make the process less annoying. In Common Lisp the function `directory` takes a pathname and returns a list of pathnames for the files in the named directory. In Rust there is the `std::fs::read_dir` that gives you an iterator that yields `io::Result<std::fs::DirEntry>`, allowing easy handling of io errors and also neatly avoiding an extra allocation. Raku has a function `dir` that returns a similar iterator, but with the added feature that it can match the names against a regex for you and only yield the matches. You can fill in more examples from your favorite languages if you want.
- kazinator 2y agoThere is a glob() function you can use in POSIX C also to get an array of strings. The getdents system call being used in the above program is the basis for implementing readdir. It doesn't return a string, but rather a buffer of multiple directory entries. The program isn't parsing a giant string; it is parsing out the directory entry structures, which are variable length and have a length field so the next one can be found. The program writes each name including the null terminator, so that the output is suitable for utilities which understand that.
- db48x 2y agoThe problem is the phrase “suitable for shell pipelines”. If you are in a shell, you should not be doing anything like this. You should use a glob directly in the shell. You should not be calling an external program, having that program print out something, and then parsing it. Just use a glob right there in your shell script. If you do anything else, you are doing it wrong. Do I really have to say this again?
- emmelaich 2y agoWow, these replies. I was being a little sarcastic as there is no 'readdir' shell command. That is all.