如何在Common Lisp中遍历目录?

26

我在Darwin上使用OpenMCL,并且我想做一些类似以下的事情:

(loop for f in (directory "somedir")
  collect (some-per-file-processing f))

但是我无法得到除了NIL以外的其它内容,而且似乎在网上也找不到什么好的解释(除了“每个系统都不一样”)。

有什么指针吗?

4个回答

31

指定路径名有两种基本方法:

  • 使用字符串

显然,字符串取决于平台:例如 Unix 语法与 Windows 语法不同。

"/Users/foo/bar.text"  is a valid pathname
"/Users/foo/*/foo.*"   is a valid pathname with two wildcards
你可以通过字符串创建一个路径名称对象:
? (pathname "/Users/bar/foo.text")
#P"/Users/bar/foo.text"

#p确保在读取时创建一个路径名对象(而不是一个字符串)。

? #P"/Users/bar/foo.text"
#P"/Users/bar/foo.text"

在内部,Common Lisp 使用路径名对象,但如果需要的话,它允许您使用普通字符串并从中创建路径名对象。

当Common Lisp看到未指定所有组件的路径名(例如目录丢失)时,它会从变量*DEFAULT-PATHNAME-DEFAULTS*的路径名对象中填充组件。

使用DESCRIBE函数可以查看路径名的组件(这里是Clozure CL):

? (describe (pathname "/Users/bar/*.text"))
#P"/Users/bar/*.text"
Type: PATHNAME
Class: #<BUILT-IN-CLASS PATHNAME>
TYPE: (PATHNAME . #<CCL::CLASS-WRAPPER PATHNAME #x3000401D03BD>)
%PATHNAME-DIRECTORY: (:ABSOLUTE "Users" "bar")
%PATHNAME-NAME: :WILD
%PATHNAME-TYPE: "text"
%PHYSICAL-PATHNAME-VERSION: :NEWEST
%PHYSICAL-PATHNAME-DEVICE: NIL
  • 使用Lisp函数创建路径名对象

MAKE-PATHNAME是该函数,它需要一些关键字参数来指定组件。

有时也有用的是基于现有路径名创建新路径名:

(make-pathname :name "foo" :defaults (pathname "/Users/bar/baz.text"))
如果你使用DIRECTORY命令,使用带有通配符的路径名会很有用。 DIRECTORY会返回匹配的路径名列表。名称“DIRECTORY”有点误导性,因为DIRECTORY不会列出目录的内容,而是针对(通常)带通配符的路径名列出匹配的路径名。通配符可以匹配组件中的字符序列,例如“/foo/s*c/list* .l*”。还有一个通配符 ** ,用于匹配目录层次结构的部分,如“/foo/** /test.lisp”,它将匹配目录foo及其子目录下所有的test.lisp文件。

(directory "/Users/foo/Lisp/**/*.lisp")

上面的代码应该返回'/Users/foo/Lisp/'目录及其所有子目录中所有的'lisp'文件列表。

如果只需要返回单个目录中的'.c'文件,请使用以下代码:

(directory "/Users/foo/c/src/*.c")
请注意,DIRECTORY返回的是路径名称对象列表(不是字符串列表)。
? (directory (make-pathname
               :name "md5"
               :type :wild
               :directory '(:absolute "Lisp" "cl-http" "cl-http-342" "server")))
(#P"/Lisp/cl-http/cl-http-342/server/md5.lisp"
 #P"/Lisp/cl-http/cl-http-342/server/md5.xfasl")

上述代码使用了由MAKE-PATHNAME创建的路径名对象。它返回所有与 /Lisp/cl-http/cl-http-342/server/md5.* 匹配的文件。

这等同于:

(directory "/Lisp/cl-http/cl-http-342/server/md5.*")

这个更短,但取决于Unix路径名的语法。


23

你的路径名中是否包含通配符?Common Lisp的路径名相关内容一开始可能有些难以理解,至少对我来说是这样...正如CLHSdirectory函数中所述:

如果pathspec不是通配符,则结果列表将包含零个或一个元素。

为了使你的路径名包括通配符,你可以尝试使用make-pathname函数,例如

(directory (make-pathname :directory '(:absolute "srv" "hunchentoot") :name :wild :type "lisp"))

甚至可以

(directory (make-pathname :directory '(:absolute "srv" "hunchentoot") :name :wild :type :wild))

我发现CL-FAD库对于处理路径名和文件系统非常有帮助。特别是,它的list-directory函数可能比普通标准的directory函数更易于使用。


3
没错,对我起作用了——(directory "pathname")返回了NIL,而(directory "pathname/.")则给出了我所期望的结果。 - Justicle
1
你只需要文件名包含点的文件吗? - Svante
有点奇怪吧?我实际上是想要.h和.cpp文件,但是“pathname / *”返回NIL。 - Justicle
1
可能与Lisp将“不完整”的路径名与一些默认值合并的方式有关。由于您的“/path/”仅包含名称的通配符,而不包含类型的通配符,我猜测CCL只是从default-pathname*或其他地方获取要查找的类型。我已经被这种不必要的假设咬了很多次,即在shell中有效的路径名/模式也是有效/等效的Lisp路径名。 - Dirk

8

现代的Common Lisp库实现目录列表功能是IOLIB

它的工作原理如下:

CL-USER> (iolib.os:list-directory "/etc/apt")
(#/p/"trusted.gpg~" #/p/"secring.gpg" #/p/"trustdb.gpg" #/p/"sources.list"
 #/p/"sources.list~" #/p/"apt-file.conf" #/p/"apt.conf.d" #/p/"trusted.gpg"
 #/p/"sources.list.d")

请注意,不需要尾部斜线或通配符。它非常强大,甚至可以处理具有不正确编码的unicode字符的文件名。
与CL-FAD相比的区别:
  • 您获得的对象是IOLIB文件路径,它替换了CL的路径名,更接近底层操作系统。
  • IOLIB使用CFFI实现其例程,因此在所有Lisp实现中都可以正常工作(只要IOLIB有操作系统的后端),而CL-FAD则尝试在所有怪癖中抽象出实现的DIRECTORY函数。
  • 与CL-FAD不同,iolib正确处理符号链接(在我看来,这是CL-FAD的一个主要问题,使其在Windows以外的平台上几乎无法使用)。

3
我会给出一个适用于我的示例代码,代码片段如下:我使用osicat(类似于cl-fad)和str编辑:还可以使用uiop:directory-files。str:contains? 可以使用search来完成。
;; searching for "ref".
(setf *data-directory* "~/books/lisp")
(remove-if-not (lambda (it)
                   (str:contains? "ref" (namestring it)))
                (osicat:list-directory *data-directory*))

返回
(#P"~/books/lisp/common-lisp-quick-reference-clqr-a4-booklet-all.pdf"
 #P"~/books/lisp/common-lisp-quick-reference-clqr-a4-consec.pdf"
 #P"~/books/lisp/commonLisp-interactive-approach-reference-buffalo.pdf")

它可以通过正确使用通配符来改进。但是,这是一个您现在可以使用的片段:)

参考资料:


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