将带有波浪号的文件名扩展为其完整路径(Common Lisp)

Expand a file name with a tilde to its fullpath (Common Lisp)

我有一个带波浪号的目录名(字符串形式):~/projects.

我想获取它的完整路径:/home/user/projects。我该怎么做?

目标是将其传递给 uiop:run-program,这似乎不正确©。


有了这个答案:How to translate (make-pathname :directory '(:absolute :home "directoryiwant") into absolute path

(merge-pathnames 
          (make-pathname
           :directory '(:relative "~/projects"))
          (user-homedir-pathname))
#P"/home/me/~/projects/"

=> 错误

谢谢。


编辑我会分享更多内容。

我想通过 uiop:launch-program 运行 一个程序。我有一个用户定义的目录列表,例如 ~/projects。使用它创建 ./~/projects 目录而不是 /home/user/projects.

如果目录不存在,

truename 不起作用。

在 SBCL 上,(namestring "~/doesntexist") returns 也是波浪号。

merge-pathnames 没用,还是波浪号问题。

将此结果提供给 ensure-directories-exist 创建了一个名为 ~.

的目录

鉴于答案,我别无选择,只能调整逻辑来扩展我们实际想要存在的目录的目录名称。

;; Create a directory
;; Ensure its name (string) ends with a slash.
(setf mydir
        (str:concat (string-right-trim (list #\/) mydir)
                    "/"))
(ensure-directories-exist base)

然后我可以使用它 truename

关于 ~

的一般评论

您的 Lisp 实现可能支持也可能不支持波浪号语法。

如果是(例如 CCL、ABCL、CLISP、ECL、LispWorks),那么 truename 将始终扩展为文件名:

(truename "~/projects")
 => /home/user/projects

如果您的实现没有,或者如果您想编写可移植的代码,则必须相对于 (user-homedir-pathname):

进行合并
(truename (merge-pathnames #p"projects" (user-homedir-pathname)))
 => /home/user/projects

请注意,波浪号(如果支持的话)似乎只支持用作路径名的字符串,而不支持目录组件; (:relative "~") 没有像您预期的那样工作,而是引用了一个名为“~”的目录。

相反,至少对于 SBCL,合适的目录是 (:absolute :home),或者,如果你想引用另一个用户,你可以将组件包装在一个列表中:

(make-pathname :directory '(:absolute (:home "root")))
=> #P"~root/"

扩展到不存在的路径名

truename would require that the thing exists?

是的,如果你想构建一个(还)不存在的文件的绝对路径,那么你需要在存在的部分上调用 truename,并与之合并。 在您的情况下,这将是 (truename "~/"),与 (user-homedir-pathname).

相同

正如 Rainer Joswig 所指出的,在 SBCL returns 以外的实现上调用 namestring 扩展路径名,将 ~ 翻译为 /home/user。在 SBCL 中,您必须调用 sb-ext:native-namestring 才能获得相同的效果。

换句话说,为了扩展到一个不一定存在的文件名,你可以编写以下可移植层:

(defun expand-file-name (pathname)
  (check-type pathname pathname)
  (block nil
    #+(or lispworks clozure cmu clisp ccl armedbear ecl)
    (return (namestring pathname))
    #+sbcl
    (return (native-namestring pathname))
    #+(not (or sbcl lispworks clozure cmu clisp ccl armedbear ecl))
    (let ((expanded (namestring pathname)))
      (prog1 expanded
        (assert (not find #\~ expanded) () 
                 "Tilde not supported")))))

如果您的 Lisp 不支持该语法,另请参阅 https://github.com/xach/tilde/blob/master/tilde.lisp 以获取灵感。

uiop中有一个native-namestring函数,应该在所有实现中都可用:

(uiop:native-namestring "~/projects")
=> /home/user/projects

Anselm Farber 的解决方案,涉及 uiop:native-namestring 在一些没有 native-namestrings 的路径名上中断,如下所示:

(uiop:native-namestring "~/Music/[Video] performance.mp4")
==>
The pathname #P"~/Music/[Video] performance.mp4"
does not have a native namestring because
of the :NAME component #<SB-IMPL::PATTERN (:CHARACTER-SET
                                           . "Video")
                                          " performance">.
   [Condition of type SB-KERNEL:NO-NATIVE-NAMESTRING-ERROR]

这是一个只使用路径名函数的直接解决方案:

(defun expand-user-homedir (f)
  (let ((d (pathname-directory f)))
    (if (and (eql (car d) :absolute)
             (eql (cadr d) :home))
        (make-pathname :directory (append (pathname-directory (user-homedir-pathname))
                                          (cddr d)) 
                       :name (pathname-name f) 
                       :type (pathname-type f))
        f)))