recursive-lengths-list-many-files 函数需要一个文件列表作为参数。在测试示例中,我们手动构造了这样的列表,但 Emacs Lisp 源码目录过大,无法手动处理。因此我们需要编写一个函数来完成这项工作,该函数会同时使用 while 循环与递归调用。
在旧版 GNU Emacs 中无需编写此类函数,因为所有 ‘.el’ 文件都放在同一目录下,我们可以直接使用 directory-files 函数,它会列出单个目录中匹配指定模式的文件名。
但新版 Emacs 将 Lisp 文件放在顶层 lisp 目录的子目录中,这种结构便于浏览。例如所有邮件相关文件都在 lisp 下名为 mail 的子目录中。但同时这也要求我们编写一个能递归进入子目录的文件列表函数。
我们可以创建名为 files-in-below-directory 的函数,结合 car、nthcdr、substring 等常用函数与现有函数 directory-files-and-attributes 实现。后者不仅会列出目录中的所有文件名(包括子目录名),还会列出其属性。
重申目标:创建一个函数,能够生成如下形式的文件列表(实际元素更多),供 recursive-lengths-list-many-files 使用:
("./lisp/macros.el"
"./lisp/mail/rmail.el"
"./lisp/hex-util.el")
directory-files-and-attributes 函数返回一个列表的列表,主列表中的每个子列表包含 13 个元素。第一个元素是文件名字符串 — 在 GNU/Linux 中可能是一个 目录文件(directory file),即具备目录特殊属性的文件。列表第二个元素:目录为 t,符号链接为链接目标字符串,否则为 nil。
例如 lisp/ 目录下第一个 ‘.el’ 文件是 abbrev.el,路径为 /usr/local/share/emacs/22.1.1/lisp/abbrev.el,它既不是目录也不是符号链接。
directory-files-and-attributes 对该文件及其属性的列出形式如下:
("abbrev.el"
nil
1
1000
100
(20615 27034 579989 697000) (17905 55681 0 0) (20615 26327 734791 805000)(18) 13188 "-rw-r--r--"
t 2971624 773)
而 lisp/ 目录下的 mail/ 是一个子目录,其列表开头如下:
("mail"
t
...
)
(如需了解各类属性含义,可查看 file-attributes 的文档。注意 file-attributes 不会列出文件名,因此它的第一个元素对应 directory-files-and-attributes 的第二个元素。)
我们希望新函数 files-in-below-directory 能够列出指定目录及其所有下层子目录中的 ‘.el’ 文件。
这提示了 files-in-below-directory 的实现思路:在目录中,将 ‘.el’ 文件名加入列表;若遇到子目录,则进入该目录重复执行操作。
不过,我们应当注意,每个目录都包含一个指向自身的名称,名为 .(“点”),以及一个指向其父目录的名称,名为 ..(“点点”)。(在根目录 / 中,.. 指向其自身,因为 / 没有父目录。)显然,我们不希望 files-in-below-directory 函数进入这些目录,因为它们总会直接或间接地将路径引回当前目录。
因此 files-in-below-directory 必须完成多项任务:
接下来编写实现这些功能的函数定义。我们使用 while 循环在目录内遍历文件名并判断处理方式,使用递归调用在每个子目录重复操作。递归模式为累积模式(see 递归模式:accumulate(累积)),使用 append 作为合并函数。
函数定义如下:
(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))
files-in-below-directory 与 directory-files 函数接收一个参数,即目录名。
在我的系统中,执行:
(length (files-in-below-directory "/usr/local/share/emacs/22.1.1/lisp/"))
可以得知 Lisp 源码目录及其子目录中共有 1031 个 ‘.el’ 文件。
files-in-below-directory 返回的列表为逆字母序。按字母序排序的表达式如下:
(sort (files-in-below-directory "/usr/local/share/emacs/22.1.1/lisp/") 'string-lessp)
If current-time-list is
nil the three timestamps are (1351051674579989697
. 1000000000), (1173477761000000000 . 1000000000), and
(1351050967734791805 . 1000000000), respectively.