Common Lisp: относительный путь к абсолютному

Может быть, это действительно глупый вопрос, но после того, как я поигрался со всеми встроенными функциями pathname-family и пакетами cl-fad/pathname-utils, я все еще не могу понять, как преобразовать относительный путь в абсолютный ( относительно $PWD):

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

Где гипотетическая функция abspath работает так же, как os.path.abspath() в Python.


person AlexDarkVoid    schedule 21.06.2017    source источник


Ответы (3)


Переменная *DEFAULT-PATHNAME-DEFAULTS* обычно содержит исходный рабочий каталог, вы можете объединить путь с этим;

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

А так как это значение по умолчанию для второго аргумента merge-pathnames, вы можете просто написать:

(defun abspath (pathname)
  (merge-pathnames pathname))
person Barmar    schedule 21.06.2017
comment
Проверено. Работает в ECL и SBCL. CLISP битый (почему-то там *default-pathname-defaults* пусто). ХОРОШО. Но как разрешить ссылки .. - cl-fad:canonical-pathname не работает? - person AlexDarkVoid; 21.06.2017

УИОП

Вот что говорится в документации UIOP о cl-fad< /сильный> :-)

UIOP полностью заменяет его лучшим дизайном и реализацией.

Большое количество реализаций поставляется с UIOP (используется ASDF3), поэтому в основном он уже доступен, когда вам это нужно (см. "Использование UIOP" в документе). Одной из многих функций, определенных в библиотеке, является uiop:parse-unix-namestring, которая понимает синтаксис имен файлов Unix, не проверяя, указывает ли путь на существующий файл или каталог. Однако двойная точка анализируется как :back или :up, что не обязательно поддерживается вашей реализацией. С SBCL дело обстоит именно так, и путь упрощается. Обратите внимание, что имена путей позволяют использовать компоненты :back и :up; :back можно легко упростить, взглянув только на путь (это синтаксический каталог up), тогда как :up является семантическим каталогом up, что означает, что он зависит от фактической файловой системы. У вас больше шансов получить каноническое имя файла, если такое имя файла существует.

Истинное имя

Вы также можете вызвать TRUENAME, что, вероятно, избавит вас от " .." компоненты на вашем пути. См. также 20.1.3 Истинные имена, которые объясняет, что вы можете указывать на один и тот же файл, используя разные пути, но обычно существует одно «каноническое» имя.

person coredump    schedule 21.06.2017
comment
Неа. (uiop:parse-unix-namestring "/very/long/way/../road/home") преобразуется в #<Unprintable pathname> (как и любой другой ввод). Что касается truename - он работает, но только когда файл реально существует, иначе сигнализирует об ошибке (что очень неудобно). - person AlexDarkVoid; 22.06.2017
comment
@AlexDarkVoid Кажется, между реализациями и/или версиями есть разница. У меня есть UIOP 3.1.5 с SBCL, который отлично работает с вашим примером. - person coredump; 22.06.2017
comment
Похоже, по крайней мере, ECL не поддерживает :BACK в компоненте каталога пути: (make-pathname :directory '(:absolute "very" "long" "way" :back "road") :name "home"). - person jkiiski; 22.06.2017

Вот окончательное решение (на основе двух предыдущих ответов):

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

uiop:parse-unix-namestring преобразует строковый аргумент в путь, заменяя ссылки . и ..; uiop:merge-pathnames* переводит относительный путь в абсолютный; uiop:unix-namestring преобразует путь обратно в строку.

Кроме того, если вы точно знаете, на какой файл указывает путь, вы можете использовать либо:

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

or

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

потому что и file-exists-p, и directory-exists-p возвращают абсолютные пути (или nil, если файл не существует).

ОБНОВЛЕНИЕ:

По-видимому, в некоторых реализациях (например, ManKai Common Lisp) uiop:merge-pathnames* не добавляет часть каталога, если в заданном пути отсутствует префикс ./ (например, если вы подаете ему #P"main.c", а не #P"./main.c"). Итак, более безопасное решение:

(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)))
person AlexDarkVoid    schedule 21.06.2017