14.9.2 生成文件列表

recursive-lengths-list-many-files 函数需要一个文件列表作为参数。在测试示例中,我们手动构造了这样的列表,但 Emacs Lisp 源码目录过大,无法手动处理。因此我们需要编写一个函数来完成这项工作,该函数会同时使用 while 循环与递归调用。

在旧版 GNU Emacs 中无需编写此类函数,因为所有 ‘.el’ 文件都放在同一目录下,我们可以直接使用 directory-files 函数,它会列出单个目录中匹配指定模式的文件名。

但新版 Emacs 将 Lisp 文件放在顶层 lisp 目录的子目录中,这种结构便于浏览。例如所有邮件相关文件都在 lisp 下名为 mail 的子目录中。但同时这也要求我们编写一个能递归进入子目录的文件列表函数。

我们可以创建名为 files-in-below-directory 的函数,结合 carnthcdrsubstring 等常用函数与现有函数 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-directorydirectory-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)

Footnotes

(18)

If current-time-list is nil the three timestamps are (1351051674579989697 . 1000000000), (1173477761000000000 . 1000000000), and (1351050967734791805 . 1000000000), respectively.