If you use f.el
, a convenient file and directory manipulation library, you only need function f-entries
.
However, if you don't want to use this library for some reason and you are ok for a non-portable *nix solution, you can use ls
command.
(defun my-directory-files (d)
(let* ((path (file-name-as-directory (expand-file-name d)))
(command (concat "ls -A1d " path "*")))
(split-string (shell-command-to-string command) "\n" t)))
The code above suffice, but for explanation read further.
Get rid of dots
According to man ls
:
-A, --almost-all
do not list implied . and ..
With split-string
that splits a string by whitespace, we can parse ls
output:
(split-string (shell-command-to-string "ls -A"))
Spaces in filenames
The problem is that some filenames may contain spaces. split-string
by default splits by regex in variable split-string-default-separators
, which is "[ \f\t\n\r\v]+"
.
-1 list one file per line
-1
allows to delimit files by newline, to pass "\n"
as a sole separator. You can wrap this in a function and use it with arbitrary directory.
(split-string (shell-command-to-string "ls -A1") "\n")
Recursion
But what if you want to recursively dive into subdirectories, returning files for future use? If you just change directory and issue ls
, you'll get filenames without paths, so Emacs wouldn't know where this files are located. One solution is to make ls
always return absolute paths. According to man ls
:
-d, --directory
list directory entries instead of contents, and do not dereference symbolic links
If you pass absolute path to directory with a wildcard and -d
option, then you'll get a list of absolute paths of immediate files and subdirectories, according to How can I list files with their absolute path in linux?. For explanation on path construction see In Elisp, how to get path string with slash properly inserted?.
(let ((path (file-name-as-directory (expand-file-name d))))
(split-srting (shell-command-to-string (concat "ls -A1d " path "*")) "\n"))
Omit null string
Unix commands have to add a trailing whitespace to output, so that prompt is on the new line. Otherwise instead of:
user@host$ ls
somefile.txt
user@host$
there would be:
user@host$ ls
somefile.txtuser@host$
When you pass custom separators to split-string
, it treats this newline as a line on its own. In general, this allows to correctly parse CSV files, where an empty line may be valid data. But with ls
we end up with a null-string, that should be omitted by passing t
as a third parameter to split-string
.
(member entry '("." ".."))
is a better way to test if a string is equal to one of a fixed set of elements. – Repugnmember
version is about 20% faster than the(or (string= ...))
version. In this case using a regexp-comparison is slower. And even when using theMATCH
argument ofdirectory-files
, it isn't faster -- so the more general approach of filtering the output afterwards with(delete nil (mapcar ..
usingmember
, that avoids messing with theMATCH
argument, is better overall. At least if one decides to using a custom function rather than usingdirectory-files
directly (which I now do). – Trifurcate