Файловый ввод-вывод это одна из базовых возможностей любого языка программирования. Без неё программы как бы замкнуты сами в себе и не имею смысла. Раньше мы ограничивались выводом на экран. Теперь пора научиться писать и читать файлы.
Посимвольное чтение
Для того, чтобы открыть файловый поток используется функция open.
(open "/some/file/name.txt")
Эта функция открывает некоторый файл в посимвольном режим. Теперь мы можем читать из него с помощью функций read-line, read-char и read. А закрыть с помощью close.
(let ((in (open "/some/file/name.txt"))) (format t "~a~%" (read-line in)) (close in))
У открытия файла есть несколько режимов. Ими управляет ключ :if-does-not-exists. Три различных значения допустимы для данного ключа:
- :error — ошибка в случае отсутствия (по-умолчанию).
- :create — создать файл, если не существует.
- NIL- вернуть nil есть файла не существует.
Все функции чтения — READ-CHAR, READ-LINE, READ — принимают опциональный аргумент, по умолчанию «истина», который указывает должны ли они сигнализировать об ошибке, если они достигли конца файла. Исправим наш пример, сделав его более стабильным.
(let ((in (open "/some/file/name.txt" :if-does-not-exist nil))) (when in (loop for line = (read-line in nil) while line do (format t "~a~%" line)) (close in)))
Двоичное чтение
Для того, чтобы открыть файл в двоичном режиме нужно передать функции open ключевой параметр :element-type со значением ‘(unsigned-byte 8). После чего мы можем читать побайтно с помощью функции read-byte.
Блочное чтение
Последняя функция для чтения, READ-SEQUENCE, работает с бинарными и символьными потоками. Она принимает последовательность, в которую нужно считать данные и ключевые аргументы :start и :end, которые указывают на подпоследовательность. Скорее всего данная функция эффективней чтения при помощи read-char или read-byte.
Файловый вывод
Для записи данных в файл необходим поток вывода, который можно получить вызовом функции OPEN с ключевым аргументом :direction :output. В случае если файл уже существует вернётся ошибка, но это поведение можно переопределить помощью ключевого аргумента :if-exists:
- :supersede — заменяет текущий файл
- :append — дозапись в конец файла
- :overwrite — перезапись с начала. Старые данные сохранятся если мы не дойдём до них.
- NIL — вернётся nil вместо потока.
Для записи в файл используются функции write-char, write-line. Если как и в случае чтения передать :element-type ‘(unsigned-byte 8), то можно записывать байты с помощью write-byte. И, наконец, симметричная функция для записи последовательностей так же существует это WRITE-SEQUENCE.
Закрытие файлов
Файлы закрываются с помощью close. Важно закрыть открытые файлы, иначе это может привести к блокировке. Поскольку не всегда удаётся избежать ошибок при открытии, то существует макрос with-open-file, который берёт на себя заботы по закрытию файла.
(with-open-file (stream "/some/file/name.txt") (format t "~a~%" (read-line stream))) (with-open-file (stream "/some/file/name.txt" :direction :output) (format stream "Какой-то текст."))
Пока на этом всё. Имён файлов коснёмся в следующий раз.