Расширение имени файла с помощью тильды до его полного пути (Common Lisp)

У меня есть имя каталога (в виде строки) с тильдой: ~/projects.

Я хочу получить полный путь: /home/user/projects. Как я могу это сделать ?

Цель состоит в том, чтобы передать его uiop:run-program, но это не совсем правильно©.


С этим ответом: Как перевести (make-pathname : directory '(:absolute :home directoryiwant) в абсолютный путь

(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") также возвращает свою тильду.

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.


person Ehvince    schedule 18.09.2019    source источник
comment
Обратите внимание, что это вообще не относится к CL; обычно ожидается, что тильды будут развернуты до того, как путь покинет интерактивную, POSIX-совместимую оболочку пользователя и будет обработан приложениями. Ни один стандартный инструмент UNIX не расширяет тильду — если вы запустите rm '~/foo.txt', он будет искать файл в каталоге с именем ~ — поэтому обычно ожидается, что приложения просто не будут попытаться поместить это в -объем.   -  person Charles Duffy    schedule 18.09.2019
comment
Хорошее напоминание!   -  person Ehvince    schedule 18.09.2019


Ответы (3)


В uiop есть функция native-namestring, которая должна быть доступна во всех реализациях:

(uiop:native-namestring "~/projects")
=> /home/user/projects
person Anselm Färber    schedule 08.01.2020
comment
Отлично спасибо! Он также работает с несуществующим именем каталога. - person Ehvince; 08.01.2020

Общие замечания о ~

Ваша реализация 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 потребует, чтобы эта вещь существовала?

Да, если вы хотите построить абсолютный путь к несуществующему (пока) файлу, вам нужно вызвать truename на той части, которая существует, и объединиться с ней. В вашем случае это будет (truename "~/"), что совпадает с (user-homedir-pathname).

Как указал Райнер Джосвиг, вызов namestring в реализациях, отличных от SBCL, возвращает расширенный путь, переводя ~ как /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")))))

См. также https://github.com/xach/tilde/blob/master/tilde.lisp для вдохновения, если ваш Lisp не поддерживает синтаксис.

person coredump    schedule 18.09.2019
comment
truename потребует, чтобы эта вещь существовала? - person Rainer Joswig; 18.09.2019
comment
Да, truename не работает, если каталог не существует. Мне бы понравилось решение, которое поддерживает это. - person Ehvince; 18.09.2019
comment
@RainerJoswig Да, кажется, нет переносимого способа разрешить имя пути, если оно не существует, но truename все еще можно вызывать для частей, которые, как ожидается, будут существовать. - person coredump; 18.09.2019
comment
namestring будет работать во многих реализациях, понимающих ~: CCL, ABCL, CLISP, ECL, LispWorks. SBCL возвращает имя тильды... - person Rainer Joswig; 18.09.2019
comment
Проблема открыта в SBCL: bugs.launchpad.net/sbcl/+bug/1853833 - person Ehvince; 25.11.2019

Решение Anselm Farber, включающее разрывы uiop:native-namestring для некоторых путей, не имеющих собственных строк имен, например:

(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)))
person jkcunningham    schedule 14.07.2021