eintr: Files List
14.9.2 Making a List of Files
-----------------------------
The ‘recursive-lengths-list-many-files’ function requires a list of
files as its argument. For our test examples, we constructed such a
list by hand; but the Emacs Lisp source directory is too large for us to
do for that. Instead, we will write a function to do the job for us.
In this function, we will use both a ‘while’ loop and a recursive call.
We did not have to write a function like this for older versions of
GNU Emacs, since they placed all the ‘.el’ files in one directory.
Instead, we were able to use the ‘directory-files’ function, which lists
the names of files that match a specified pattern within a single
directory.
However, recent versions of Emacs place Emacs Lisp files in
sub-directories of the top level ‘lisp’ directory. This re-arrangement
eases navigation. For example, all the mail related files are in a
‘lisp’ sub-directory called ‘mail’. But at the same time, this
arrangement forces us to create a file listing function that descends
into the sub-directories.
We can create this function, called ‘files-in-below-directory’, using
familiar functions such as ‘car’, ‘nthcdr’, and ‘substring’ in
conjunction with an existing function called
‘directory-files-and-attributes’. This latter function not only lists
all the filenames in a directory, including the names of
sub-directories, but also their attributes.
To restate our goal: to create a function that will enable us to feed
filenames to ‘recursive-lengths-list-many-files’ as a list that looks
like this (but with more elements):
("./lisp/macros.el"
"./lisp/mail/rmail.el"
"./lisp/makesum.el")
The ‘directory-files-and-attributes’ function returns a list of
lists. Each of the lists within the main list consists of 13 elements.
The first element is a string that contains the name of the file—which,
in GNU/Linux, may be a “directory file”, that is to say, a file with the
special attributes of a directory. The second element of the list is
‘t’ for a directory, a string for symbolic link (the string is the name
linked to), or ‘nil’.
For example, the first ‘.el’ file in the ‘lisp/’ directory is
‘abbrev.el’. Its name is ‘/usr/local/share/emacs/22.1.1/lisp/abbrev.el’
and it is not a directory or a symbolic link.
This is how ‘directory-files-and-attributes’ lists that file and its
attributes:
("abbrev.el"
nil
1
1000
100
(20615 27034 579989 697000)
(17905 55681 0 0)
(20615 26327 734791 805000)
13188
"-rw-r--r--"
t
2971624
773)
On the other hand, ‘mail/’ is a directory within the ‘lisp/’
directory. The beginning of its listing looks like this:
("mail"
t
...
)
(To learn about the different attributes, look at the documentation
of ‘file-attributes’. Bear in mind that the ‘file-attributes’ function
does not list the filename, so its first element is
‘directory-files-and-attributes’’s second element.)
We will want our new function, ‘files-in-below-directory’, to list
the ‘.el’ files in the directory it is told to check, and in any
directories below that directory.
This gives us a hint on how to construct ‘files-in-below-directory’:
within a directory, the function should add ‘.el’ filenames to a list;
and if, within a directory, the function comes upon a sub-directory, it
should go into that sub-directory and repeat its actions.
However, we should note that every directory contains a name that
refers to itself, called ‘.’ (“dot”), and a name that refers to its
parent directory, called ‘..’ (“dot dot”). (In ‘/’, the root directory,
‘..’ refers to itself, since ‘/’ has no parent.) Clearly, we do not
want our ‘files-in-below-directory’ function to enter those directories,
since they always lead us, directly or indirectly, to the current
directory.
Consequently, our ‘files-in-below-directory’ function must do several
tasks:
• Check to see whether it is looking at a filename that ends in
‘.el’; and if so, add its name to a list.
• Check to see whether it is looking at a filename that is the name
of a directory; and if so,
− Check to see whether it is looking at ‘.’ or ‘..’; and if so
skip it.
− Or else, go into that directory and repeat the process.
Let’s write a function definition to do these tasks. We will use a
‘while’ loop to move from one filename to another within a directory,
checking what needs to be done; and we will use a recursive call to
repeat the actions on each sub-directory. The recursive pattern is
Accumulate (Accumulate), using ‘append’ as the combiner.
Here is the function:
(defun files-in-below-directory (directory)
"List the .el files in DIRECTORY and in its sub-directories."
;; Although the function will be used non-interactively,
;; it will be easier to test if we make it interactive.
;; The directory will have a name such as
;; "/usr/local/share/emacs/22.1.1/lisp/"
(interactive "DDirectory name: ")
(let (el-files-list
(current-directory-list
(directory-files-and-attributes directory t)))
;; while we are in the current directory
(while current-directory-list
(cond
;; check to see whether filename ends in '.el'
;; and if so, add its name to a list.
((equal ".el" (substring (car (car current-directory-list)) -3))
(setq el-files-list
(cons (car (car current-directory-list)) el-files-list)))
;; check whether filename is that of a directory
((eq t (car (cdr (car current-directory-list))))
;; decide whether to skip or recurse
(if
(equal "."
(substring (car (car current-directory-list)) -1))
;; then do nothing since filename is that of
;; current directory or parent, "." or ".."
()
;; else descend into the directory and repeat the process
(setq el-files-list
(append
(files-in-below-directory
(car (car current-directory-list)))
el-files-list)))))
;; move to the next filename in the list; this also
;; shortens the list so the while loop eventually comes to an end
(setq current-directory-list (cdr current-directory-list)))
;; return the filenames
el-files-list))
The ‘files-in-below-directory’ ‘directory-files’ function takes one
argument, the name of a directory.
Thus, on my system,
(length
(files-in-below-directory "/usr/local/share/emacs/22.1.1/lisp/"))
tells me that in and below my Lisp sources directory are 1031 ‘.el’
files.
‘files-in-below-directory’ returns a list in reverse alphabetical
order. An expression to sort the list in alphabetical order looks like
this:
(sort
(files-in-below-directory "/usr/local/share/emacs/22.1.1/lisp/")
'string-lessp)