Emacs Lisp:获取`directory-files`时,不包含“.”和“..”的简洁方法是什么?

20

函数directory-files会返回...条目。虽然从某种意义上说这是正确的,因为只有这样函数才能返回所有现有的条目,但我还没有看到包含它们的用途。另一方面,每次使用directory-files时,我也会写类似于

的东西。

(unless (string-match-p "^\\.\\.?$" ... 

或者为了更好的效率

(unless (or (string= "." entry)
            (string= ".." entry))
   ..)
特别是在交互式使用(M-:),额外的代码是不必要的。 有没有一些预定义的函数可以有效地返回目录的实际子条目?

3
(member entry '("." "..")) 是测试字符串是否等于一个固定集合中的元素的更好方法。 - Stefan
1
很好的观点。我刚刚进行了基准测试,除了更易于阅读之外,member版本比(or (string= ...))版本快约20%。在这种情况下,使用正则表达式比较会更慢。即使使用directory-filesMATCH参数,它也不会更快——因此,使用更通用的方法,在输出后使用(delete nil (mapcar .. 使用member过滤,避免与MATCH参数混淆,总体上更好。至少如果一个人决定使用自定义函数而不是直接使用directory-files(我现在这样做)。 - kdb
3个回答

22
你可以在原始函数调用的一部分中完成这个操作。
(directory-files DIRECTORY &optional FULL MATCH NOSORT)

If MATCH is non-nil, mention only file names that match the regexp MATCH.
所以:
(directory-files (expand-file-name "~/") nil "^\\([^.]\\|\\.[^.]\\|\\.\\..\\)")

或者:

(defun my-directory-files (directory &optional full nosort)
  "Like `directory-files' with MATCH hard-coded to exclude \".\" and \"..\"."
  (directory-files directory full "^\\([^.]\\|\\.[^.]\\|\\.\\..\\)" nosort))

尽管更贴近你自己的方法可能会形成一个更高效的包装器。

(defun my-directory-files (directory &optional full match nosort)
  "Like `directory-files', but excluding \".\" and \"..\"."
  (delete "." (delete ".." (directory-files directory full match nosort))))

虽然这样会对列表进行两次处理,但我们知道我们希望排除的每个名称只有一个实例(而且有很大几率它们会首先出现),因此如果您经常需要处理大型目录,则采用以下更为合适:

(defun my-directory-files (directory &optional full match nosort)
  "Like `directory-files', but excluding \".\" and \"..\"."
  (let* ((files (cons nil (directory-files directory full match nosort)))
         (parent files)
         (current (cdr files))
         (exclude (list "." ".."))
         (file nil))
    (while (and current exclude)
      (setq file (car current))
      (if (not (member file exclude))
          (setq parent current)
        (setcdr parent (cdr current))
        (setq exclude (delete file exclude)))
      (setq current (cdr current)))
    (cdr files)))

那么,没有简单的内置功能可用吗? - kdb
18
你可以使用(directory-files "~/" t directory-files-no-dot-files-regexp)来获得相同的结果。 - squiter
3
@squiter 你的回答最好,但是没有人看到它。 - Jean Louis

9
如果你使用方便的文件和目录操作库f.el,只需要函数f-entries即可。
然而,如果由于某种原因你不想使用这个库,并且可以接受非便携式*nix解决方案,你可以使用ls命令
(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)))

以下代码已经足够,但是如果需要解释,请继续阅读。

去除点

根据 man ls

   -A, --almost-all
          do not list implied . and ..

使用split-string函数可以将字符串按空格分割,我们可以解析ls命令的输出:

(split-string (shell-command-to-string "ls -A"))

文件名中的空格

问题在于有些文件名可能包含空格。默认情况下,split-string 函数会使用变量split-string-default-separators中的正则表达式进行拆分,该变量的值为 "[ \f\t\n\r\v]+"

   -1     list one file per line

-1允许通过换行符来分隔文件,可以将"\n"作为唯一的分隔符传递。您可以将其封装在一个函数中,并与任意目录一起使用。

(split-string (shell-command-to-string "ls -A1") "\n")

递归

但是如果您想递归地深入子目录,返回文件以备将来使用呢? 如果您只更改目录并发出 ls 命令,则会得到没有路径的文件名,因此Emacs不知道这些文件位于哪里。 一个解决方案是使 ls 始终返回绝对路径。 根据 man ls

   -d, --directory
          list directory entries instead of contents, and do not dereference symbolic links

如果您使用通配符并将绝对路径传递给目录,再使用-d选项,则可以根据如何在Linux中列出文件的绝对路径?获取立即文件和子目录的绝对路径列表。有关路径构建的说明,请参见Elisp中如何正确插入斜杠以获取路径字符串?

(let ((path (file-name-as-directory (expand-file-name d))))
  (split-srting (shell-command-to-string (concat "ls -A1d " path "*")) "\n"))

省略空字符串

Unix命令需要在输出时添加尾随空格,以便提示符出现在新行上。否则会变成:

user@host$ ls
somefile.txt
user@host$

这里会有:

user@host$ ls
somefile.txtuser@host$

当您将自定义分隔符传递给 split-string 时,它会将这个换行符视为独立的一行。通常,这允许正确解析 CSV 文件,其中空行可能是有效数据。但是,在使用 ls 时,我们最终得到一个空字符串,应通过将 t 作为第三个参数传递给 split-string 来省略它。

2
那不是最好的答案,最佳答案在下面:(directory-files "/tmp" t directory-files-no-dot-files-regexp) - Jean Louis

1

直接使用 remove-if 怎么样?

(remove-if (lambda (x) (member x '("." "..")))
           (directory-files path))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接