Search code examples
pathcommon-lispsbcl

How to append a subdirectory to a pathname in Common Lisp


I'm having some trouble with path manipulation in Common Lisp (using SBCL). I'm trying to append a subdirectory name to an absolute pathname that I have.

Example: I am running my repl in directory #P"/home/me/somedir" and I have "otherdir" as a variable and what I want is #P"/home/me/somedir/otherdir"

Essentially, I'm trying to translate how I'd do this in Python into Common Lisp: os.path.join(os.getcwd(), "otherdir")

I've tried (merge-pathnames (sb-posix:getcwd) "otherdir/") but I just get the cwd back. If I try (merge-pathnames "otherdir/" (sb-posix:getcwd)) I instead get otherdir/ prepended before the last directory: #P"/home/me/otherdir/somedir"

I've also tried using (make-pathname :directory '(:relative "otherdir") :defaults (sb-posix:getcwd)) but I instead get #P"otherdir/somedir".

Does anyone know how to build up a path programmatically in Common Lisp?


Solution

  • Ah, path handling mysteries… you were nearly there with merge-pathnames, but the second argument must have a trailing /:

    (sb-posix:getcwd)
    "/home/vince/projets"
    

    => no trailing /, so accordingly we get 2 unexpected results when using otherdir/ (trailing slash) or otherdir:

    (merge-pathnames "otherdir/" (sb-posix:getcwd))
    #P"/home/vince/otherdir/projets"
    ;;                      ^^
    
    (merge-pathnames "otherdir" (sb-posix:getcwd))
    #P"/home/vince/otherdir"  ;; no "projets"
    

    Let's use a trailing / on the right:

    (merge-pathnames "otherdir" "/home/vince/projets/")
    #P"/home/vince/projets/otherdir"  ;; <= expected
    

    So is there a more "correct" cwd? As often, the solution is given by UIOP (shipped in ASDF, so always available).

    TBH I didn't know it before, but I looked it up:

    (apropos "cwd")
    :GETCWD (bound)
    OSICAT::CWD
    OSICAT-POSIX::%GETCWD (fbound)
    OSICAT-POSIX:GETCWD (fbound)
    SB-POSIX:GETCWD (fbound)
    SB-UNIX:POSIX-GETCWD (fbound)
    SB-UNIX:POSIX-GETCWD/ (fbound)
    SB-X86-64-ASM::CWD (fbound)
    SB-X86-64-ASM::CWDE (fbound)
    UIOP/FILESYSTEM::CWD
    UIOP/OS:GETCWD (fbound)
    

    (YMMV)

    So:

    (UIOP/OS:GETCWD)
    #P"/home/vince/projets/"
    ;;                    ^ yes!
    

    and so,

    The solution

    (merge-pathnames "otherdir" (UIOP/OS:GETCWD))
    #P"/home/vince/projets/otherdir"
    

    UIOP is a portable library. What's the implementation for SBCL? Looking at the source (M-.):

    (sb-ext:parse-native-namestring (sb-unix:posix-getcwd/))