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