Streams, Files and Pathnames
29.1 Streams
*standard-output* *standard-input* *error-output* *terminal-io* *trace-output* *query-io*
(write-line "text") ; write a string and a newline
(write-string "no newline")
(write-char #\x)
(terpri) ; newline
(fresh-line) ; newline unless at the start of a line
(print 42) (prin1 "a") (princ "a") (pprint '(defun f (x) x))
(read-line) ; read a line from *standard-input*
(read) ; read a Lisp object
(finish-output) ; flush
(with-output-to-string (s) (format s "~a and ~a" 1 2)) ; string output stream
(with-input-from-string (s "1 2 3")
(loop for x = (read s nil :eof) until (eq x :eof) collect x)) ; (1 2 3)
(make-broadcast-stream) ; a sink (discards output) or a fan-out to several streams
(make-two-way-stream in out) (make-concatenated-stream s1 s2) (make-echo-stream in out)29.2 Files
;; Write
(with-open-file (out "notes.txt" :direction :output
:if-exists :supersede ; or :append, :error
:if-does-not-exist :create
:external-format :utf-8)
(format out "Line 1~%")
(write-line "Line 2" out))
;; Read line by line
(with-open-file (in "notes.txt" :if-does-not-exist nil)
(when in
(loop for line = (read-line in nil)
while line
collect line))) ; ("Line 1" "Line 2")
;; Whole file / convenience (UIOP)
(uiop:read-file-string "notes.txt")
(uiop:read-file-lines "notes.txt")
(alexandria:write-string-into-file "content" "out.txt" :if-exists :supersede)
;; Binary
(with-open-file (in "image.png" :element-type '(unsigned-byte 8))
(let ((header (make-array 8 :element-type '(unsigned-byte 8))))
(read-sequence header in)
header))
;; Lisp data files: write and read s-expressions directly
(with-open-file (out "data.sexp" :direction :output :if-exists :supersede)
(with-standard-io-syntax (print '(:users ((:name "Ana" :age 30))) out)))
(with-open-file (in "data.sexp")
(with-standard-io-syntax (let ((*read-eval* nil)) (read
…29.3 Pathnames and the File System
CL pathnames predate modern file systems and can be quirky. UIOP functions are the portable, practical choice.
#p"/home/ana/notes.txt"
(pathname-name #p"/tmp/report.txt") ; "report"
(pathname-type #p"/tmp/report.txt") ; "txt"
(pathname-directory #p"/tmp/data/x.csv") ; (:ABSOLUTE "tmp" "data")
(make-pathname :name "out" :type "csv" :defaults #p"/tmp/") ; #P"/tmp/out.csv"
(merge-pathnames "data/x.csv" #p"/home/ana/") ; #P"/home/ana/data/x.csv"
(probe-file "notes.txt") ; truename or NIL
(directory "/tmp/*.txt") ; list matching files
(ensure-directories-exist "/tmp/new/dir/")
(delete-file "old.txt")
(rename-file "a.txt" "b.txt")
(file-write-date "notes.txt") ; universal time
(uiop:directory-files "/tmp/")
(uiop:subdirectories "/tmp/")
(uiop:getcwd)
(uiop:native-namestring #p"~/x.txt")
(uiop:with-temporary-file (:pathname p) (print p))
(asdf:system-relative-pathname "inventory" "data/items.csv") ; files inside your project