Файлы и директории
Проверка существования файла
Для проверки существования файла используйте функцию probe-file, которая в зависимости от наличия возвращает nil или реальное имя (truename) файла, которое не обязательно совпадает с поданным аргументом (например, в случае жесткой ссылки, hardlink).
(probe-file "/etc/passwd")
#p"/etc/passwd"
(probe-file "/etc/shadow")
#p"/etc/shadow"
(probe-file "/etc/master.passwd")
nil
Пример приведен для linux. В linux пароли пользователей хранятся в /etc/shadow, во freebsd - в /etc/master.passwd. Файл /etc/passwd есть в обеих системах.
Открытие файла
В Коммон Лиспе есть функции open и close, которые похожи на одноименные функции в других языках программирования. Тем не менее, практически всегда рекомендуется использовать вместо них макрос with-open-file. Он не только выполнит откроет файл и закроет его после выполнения операций, но и позаботится о его закрытии, если выполнение одной из этих операций приведет к аварийному завершению работы (например, при использовании throw. Типичный шаблон использования with-open-file выглядит следующим образом:
(with-open-file (str <file-spec>
:direction <direction>
:if-exists <if-exists>
:if-does-not-exist <if-does-not-exist>)
<your code here>)
* str - переменная, которая будет связана с создаваемым при открытии файла потоком.
* <file-spec> - имя файла или путь к нему.
* <direction> может быть :input (чтение из файла), :output (запись в файл) или :io (чтение и запись одновременно. По умолчанию :input.
* <if-exists> определяет поведение в случае попытки записи в существующий файл. По умолчанию используется :error. Также вы можете использовать :supersede (замена файла), nil (переменная, ранее связанная с потоком, получит значение nil и запись в файл производиться не будет) или :rename (существующий файл переименовывается). В режиме чтения эта опция игнорируется.
* <if-does-not-exist> определяет, что делать в случае отсутствия файла. Возможные варианты: :error, :create (создается пустой файл) или nil (поток перенаправляется в nil). Поведение по умолчанию см. CLHS.
Есть и другие опции, о которых вы можете прочитать в CLHS
Чтение строки из файла
Функция read-line читает одну строку из потока (стандатрный ввод по умолчанию). Конец строки определяется по символу окончания строки. Она вернет строку без этого символа. read-line также возвращает второе значение, которое истинно, если строка была завершена без символа новой строки (фактически означает окончание файла). По умолчанию read-line вернет ошибку, если будет достигнут конец файла. Этого можно избежать, подавая nil в качестве второго аргумента. В последнем случае в случае завершения файла функция вернет nil.
(with-open-file (stream "/etc/passwd")
(do ((line (read-line stream nil)
(read-line stream nil)))
((null line))
(print line)))
Если необходимо остановиться после нахождения какого-то определенного символа, этот символ подается третьим аргументом read-line:
(with-open-file (stream "/etc/passwd")
(loop for line = (read-line stream nil 'foo)
until (eq line 'foo)
do (print line)))
Чтение одного символа (char)
read-char очень похожа на read-line, но читает за раз один символ, а не строку. Разумеется, символ перехода на новую строку здесь не считается особым:
(with-open-file (stream "/etc/passwd")
(do ((char (read-char stream nil)
(read-char stream nil)))
((null char))
(print char)))
Чтение символа без его удаления
Чтобы прочитать из потока символ без его удаления из этого потока, воспользуйтесь функцией peek-char. Вообще говоря, она может быть использована с тремя различными целями в зависимости от значения первого аргумента. Если первый аргумент nil, то она вернет следующий символ, ожидающий своей очереди в потоке:
(with-input-from-string (stream "I'm not amused")
(print (read-char stream))
(print (peek-char nil stream))
(print (read-char stream))
(values))
#\I
#\'
#\'
Если первый аргумент t, то peak-char будет пропускать пустые (whitespace) символы так, как будто все пробелы, переносы и табуляторы были прочитаны функцией read-char:
(with-input-from-string (stream "I'm
not amused")
(print (read-char stream))
(print (read-char stream))
(print (read-char stream))
(print (peek-char t stream))
(print (read-char stream))
(print (read-char stream))
(values))
#\I
#\'
#\m
#\n
#\n
#\o
Если первый аргумент - символ, то peak-char пропускает все символы, пока не встретит этот символ.
(with-input-from-string (stream "I'm not amused")
(print (read-char stream))
(print (peek-char #\a stream))
(print (read-char stream))
(print (read-char stream))
(values))
#\I
#\a
#\a
#\m
read-char имеет другие опционные аргументы, которые определяют поведение в случае достижения конца файла, так же, как и аналогичные в read-line и read-char:
(with-input-from-string (stream "I'm not amused")
(print (read-char stream))
(print (peek-char #\d stream))
(print (read-char stream))
(print (peek-char nil stream nil 'the-end))
(values))
#\I
#\d
#\d
THE-END
Вы также можете положить один символ назад в поток с помощью функции unread-char. Используйте его так, как если бы вы захотели использовать reak-char вместо read-char уже после прочтения символа:
(with-input-from-string (stream "I'm not amused")
(let ((c (read-char stream)))
(print c)
(unread-char c stream)
(print (read-char stream))
(values)))
#\I
#\I
Учтите, что с помощью unread-char использовать поток как стек не получится. Вы можете вернуть в поток только один символ, притом только тот, который был прочтен непосредственно перед этим. Вы не можете вернуть никакой символ, если не было прочитано ничего.
Свободный доступ к файлу (random access)
Для свободного доступа к любому месту файла используйте file position. Если функция получает только один аргумент (сам поток), то она возвращает текущее положение в файле. Изменить текущее положение можно с помощью второго аргумента, который должен быть целым числом:
(with-input-from-string (stream "I'm not amused")
(print (file-position stream))
(print (read-char stream))
(print (file-position stream))
(file-position stream 4)
(print (file-position stream))
(print (read-char stream))
(print (file-position stream))
(values))
0
#\I
1
4
#\n
5