Search code examples
common-lisppathname

Common Lisp: relative path to absolute


May be it is a really dumb question, but after playing around with all built-in pathname-family functions and cl-fad/pathname-utils packages I still can't figure out how to convert a relative path to absolute (with respect to $PWD):

; let PWD be "/very/long/way"
(abspath "../road/home"); -> "/very/long/road/home"

Where the hypothetical function abspath works just like os.path.abspath() in Python.


Solution

  • Here's the final solution (based on the previous two answers):

    (defun abspath
           (path-string)
       (uiop:unix-namestring
        (uiop:merge-pathnames*
         (uiop:parse-unix-namestring path-string))))
    

    uiop:parse-unix-namestring converts the string argument to a pathname, replacing . and .. references; uiop:merge-pathnames* translates a relative pathname to absolute; uiop:unix-namestring converts the pathname back to a string.

    Also, if you know for sure what kind of file the path points to, you can use either:

    (uiop:unix-namestring (uiop:file-exists-p path))
    

    or

    (uiop:unix-namestring (uiop:directory-exists-p path))
    

    because both file-exists-p and directory-exists-p return absolute pathnames (or nil, if file does not exist).

    UPDATE:

    Apparently in some implementations (like ManKai Common Lisp) uiop:merge-pathnames* does not prepend the directory part if the given pathname lacks ./ prefix (for example if you feed it #P"main.c" rather than #P"./main.c"). So the safer solution is:

    (defun abspath
           (path-string &optional (dir-name (uiop:getcwd)))
       (uiop:unix-namestring
        (uiop:ensure-absolute-pathname
         (uiop:merge-pathnames*
          (uiop:parse-unix-namestring path-string))
         dir-name)))