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
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
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
using familiar functions such as
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
as a list that looks like this (but with more elements):
("./lisp/macros.el" "./lisp/mail/rmail.el" "./lisp/makesum.el")
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
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
("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
function does not list the filename, so its first element is
directory-files-and-attributes’s second element.)
We will want our new function,
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
files-in-below-directory function must do
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
(see Recursive Pattern: accumulate),
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))
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
(sort (files-in-below-directory "/usr/local/share/emacs/22.1.1/lisp/") 'string-lessp)