AlexDarkVoid
AlexDarkVoid

Reputation: 542

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.

Upvotes: 9

Views: 3028

Answers (3)

AlexDarkVoid
AlexDarkVoid

Reputation: 542

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)))

Upvotes: 3

coredump
coredump

Reputation: 38789

UIOP

Here is what the documentation of UIOP says about cl-fad :-)

UIOP completely replaces it with better design and implementation

A good number of implementations ship with UIOP (used by ASDF3), so it's basically already available when you need it (see "Using UIOP" in the doc.). One of the many functions defined in the library is uiop:parse-unix-namestring, which understands the syntax of Unix filenames without checking if the path designates an existing file or directory. However the double-dot is parsed as :back or :up which is not necessarily supported by your implementation. With SBCL, it is the case and the path is simplified. Note that pathnames allows to use both :back and :up components; :back can be simplified easily by looking at the pathname only (it is a syntactic up directory), whereas :up is the semantic up directory, meaning that it depends on the actual file system. You have a better chance to obtain a canonical file name if the file name exists.

Truename

You can also call TRUENAME, which will probably get rid of the ".." components in your path. See also 20.1.3 Truenames which explains that you can point to the same file by using different pathnames, but that there is generally one "canonical" name.

Upvotes: 4

Barmar
Barmar

Reputation: 780673

The variable *DEFAULT-PATHNAME-DEFAULTS* usually contains your initial working directory, you can merge the pathname with that;

(defun abspath (pathname)
  (merge-pathnames pathname *default-pathname-defaults*))

And since this is the default for the second argument to merge-pathnames, you can simply write:

(defun abspath (pathname)
  (merge-pathnames pathname))

Upvotes: 6

Related Questions