【问题标题】:How do I iterate through a directory in Common Lisp?如何遍历 Common Lisp 中的目录?
【发布时间】:2010-11-27 02:02:45
【问题描述】:

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

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

但我无法让directory 返回除NIL 以外的任何内容,而且我似乎在网上找不到任何好的解释(除了“每个系统的不同”)。

任何指针?

【问题讨论】:

    标签: lisp filesystems directory common-lisp


    【解决方案1】:

    您的路径名规范是否包含通配符? Common Lisp 的路径名一开始有点难以掌握——至少对我来说是……正如CLHS 在directory 函数中所说的那样:

    如果 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 函数更易于使用。

    【讨论】:

    • 是的,对我有用 - (directory "pathname") 返回 NIL,其中 (directory "pathname/*.*") 给了我预期的结果。
    • 您只需要名称中包含点的文件吗?
    • 很奇怪吧?我实际上是在 .h 和 .cpp 文件之后,但是“pathname/*”返回 NIL。
    • 可能与 Lisp 将“不完整”路径名与一些默认值合并的方式有关。由于您的“/path/*”只包含名称的通配符,而不包含类型的通配符,我猜 CCL 只是从 default-pathname 或其他东西中获取类型。我经常被无根据的假设所困扰,即在 shell 中有效的路径名/模式也是有效/等效的 Lisp 路径名
    【解决方案2】:

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

    • 使用字符串

    字符串显然取决于平台:例如 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 路径名语法。

    【讨论】:

      【解决方案3】:

      实现目录列表的现代 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 恕我直言之外的平台上使用)。

      【讨论】:

        【解决方案4】:

        为了代码 sn-p,我将添加一个适合我的示例。我使用osicat(类似于cl-fad)和str。

        编辑:也可以使用uiop:directory-files。 str:包含?可以通过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")
        

        正确使用通配符当然可以改善它。但是,这是您现在可以使用的 sn-p :)

        参考资料:

        【讨论】:

          猜你喜欢
          • 2023-04-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-10-04
          • 2011-07-23
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多