Emacs lisp: `directory-files`

Функция directory-files возвращает записи . и ... Хотя в некотором смысле это правда, что только так функция возвращает все существующие записи, я пока не вижу возможности их включения. С другой стороны, каждый раз при использовании directory-files я также пишу что-то вроде

(unless (string-match-p "^\\.\\.?$" ... 

или для повышения эффективности

(unless (or (string= "." entry)
            (string= ".." entry))
   ..)

В частности, при интерактивном использовании (M-:) дополнительный код нежелателен.

Есть ли какая-либо предопределенная функция, которая эффективно возвращает только фактические подстатки каталога?

Ответ 1

Вы можете сделать это как часть исходного вызова функции.

(directory-files DIRECTORY &optional FULL MATCH NOSORT)

If MATCH is non-nil, mention only file names that match the regexp MATCH.

так:

(directory-files (expand-file-name "~/") nil "^\\([^.]\\|\\.[^.]\\|\\.\\..\\)")

или

(defun my-directory-files (directory &optional full nosort)
  "Like `directory-files' with MATCH hard-coded to exclude \".\" and \"..\"."
  (directory-files directory full "^\\([^.]\\|\\.[^.]\\|\\.\\..\\)" nosort))

хотя нечто более похожее на ваш собственный подход может сделать для более эффективной обертки, действительно.

(defun my-directory-files (directory &optional full match nosort)
  "Like `directory-files', but excluding \".\" and \"..\"."
  (delete "." (delete ".." (directory-files directory full match nosort))))

хотя эта обработка выполняется дважды, и мы знаем только один экземпляр каждого из имен, которые мы хотим исключить (и там есть вероятность, что они появятся вначале), поэтому что-то более похожее на это может быть хорошим решением, если вы часто будете иметь дело с большими каталогами:

(defun my-directory-files (directory &optional full match nosort)
  "Like `directory-files', but excluding \".\" and \"..\"."
  (let* ((files (cons nil (directory-files directory full match nosort)))
         (parent files)
         (current (cdr files))
         (exclude (list "." ".."))
         (file nil))
    (while (and current exclude)
      (setq file (car current))
      (if (not (member file exclude))
          (setq parent current)
        (setcdr parent (cdr current))
        (setq exclude (delete file exclude)))
      (setq current (cdr current)))
    (cdr files)))

Ответ 2

Если вы используете f.el, удобную библиотеку манипуляции с файлами и каталогами, вам нужна функция f-entries.

Однако, если вы не хотите использовать эту библиотеку по какой-то причине, и вы в порядке для непереносимого решения * nix, вы можете использовать ls команда.

(defun my-directory-files (d)
  (let* ((path (file-name-as-directory (expand-file-name d)))
         (command (concat "ls -A1d " path "*")))
    (split-string (shell-command-to-string command) "\n" t)))

Код выше достаточно, но для пояснения читайте далее.

Избавиться от точек

Согласно man ls:

   -A, --almost-all
          do not list implied . and ..

С split-string, который разбивает строку по пробелам, мы можем разобрать ls вывод:

(split-string (shell-command-to-string "ls -A"))

Пробелы в именах файлов

Проблема в том, что некоторые имена файлов могут содержать пробелы. split-string по умолчанию разбивается на regex в переменной split-string-default-separators, которая "[ \f\t\n\r\v]+".

   -1     list one file per line

-1 позволяет разграничить файлы с помощью новой строки, передать "\n" в качестве единственного разделителя. Вы можете обернуть это в функцию и использовать ее с произвольным каталогом.

(split-string (shell-command-to-string "ls -A1") "\n")

Рекурсия

Но что, если вы хотите рекурсивно погрузиться в подкаталоги, возвращая файлы для будущего использования? Если вы просто сменили каталог и выпустили ls, вы получите имена файлов без путей, поэтому Emacs не будет знать, где находятся эти файлы. Одно из решений заключается в том, чтобы ls всегда возвращал абсолютные пути. Согласно man ls:

   -d, --directory
          list directory entries instead of contents, and do not dereference symbolic links

Если вы передадите абсолютный путь в каталог с подстановочным знаком и -d, то вы получите список абсолютных путей для немедленных файлов и подкаталогов, в соответствии с Как я могу список файлов с их абсолютным путем в linux?. Для объяснения в построении пути см. В Elisp, как получить строку пути со скобой, правильно вставленной?.

(let ((path (file-name-as-directory (expand-file-name d))))
  (split-srting (shell-command-to-string (concat "ls -A1d " path "*")) "\n"))

Опустить нулевую строку

Команды Unix должны добавить конечные пробелы для вывода, так что приглашение находится в новой строке. В противном случае вместо:

[email protected]$ ls
somefile.txt
[email protected]$

было бы:

[email protected]$ ls
[email protected]$

Когда вы передаете пользовательские разделители на split-string, он обрабатывает эту новую строку как строку самостоятельно. В общем, это позволяет правильно разобрать CSV файлы, где пустая строка может быть действительными данными. Но с ls мы получаем нулевую строку, которую следует опустить, передав t в качестве третьего параметра в split-string.