7. File Access

Introduction

;;;-----------------------------
(with-open-file (input "/usr/local/widgets/data")
  ;; No need for "die" like call here b/c WITH-OPEN-FILE will do it
  ;; automatically.
  (iter (for line in-stream input using 'read-line)
        (when (scan "blue" line)
          (format t "~A~%" line)))
  ;; No need for explicit CLOSE here b/c WITH-OPEN-FILE will do it
  ;; automatically.
  )
;;;-----------------------------
(let ((var *standard-input*))
  (mysub var logfile))
;;;-----------------------------
;; The Perl example here is showing the "object-oriented" style of
;; file manipulation.  In CL it isn't any different than the above.
;; However we will use this opportunity to demonstrate how to
;; "manually" open/close a file without WITH-OPEN-FILE.
(let ((input (open "/usr/local/widgets/data")))
  (iter (for line in-stream input using 'read-line)
        (setf line (chomp line))
        (when (scan "blue" line)
          (format t "~A~%" line)))
  ;; Don't do this, either use WITH-OPEN-FILE or use UNWIND-PROTECT as
  ;; illustrated later.
  (close line))
;;;-----------------------------
(unwind-protect 
    (progn
      (iter (for line in-stream *standard-input* using 'read-line)
            (unless (scan "\\d" line)
              (warn "No digit found.~%"))
            (format t "Read: ~A~%" line)))
  ;; Not normally a good idea to do the following; just matching the
  ;; Perl.
  (close *standard-output*))
;;;-----------------------------
;; No need for explicit die, OPEN will throw an exception.
(defparameter *logfile* (open "/tmp/log" :direction :output))
;;;-----------------------------
(close *fh*)                            ; no need for die()
;;;-----------------------------
(let ((*standard-output* *logfile*))    ; switch to *LOGFILE* for output
  (format t "Countdown initiated ...~%"))
;; return to original output
(format t "You have 30 seconds to reach minimum safety distance.~%")
;;;-----------------------------

Opening a File

;;;-----------------------------
;; For reading is the default, no need for "<" or equivalent.  No need
;; for explicit die()-like call either.  Note also that you should use
;; WITH-OPEN-FILE instead of a raw OPEN wherever possible.
(defparameter *source* (open path)) 

(defparameter *sink* (open path :direction :output)) 
;;;-----------------------------
#+sbcl
(progn
  (defparameter *source* (sb-posix:open path sb-posix:o-rdonly))

  (defparameter *sink* (sb-posix:open path sb-posix:o-wronly)))
;;;-----------------------------
;; There is no equivalent of Perl's "object-oriented" file interface
;; (arguably, the standard mechanism is already object-oriented).
;;;-----------------------------
#+sbcl
(progn
  (defparameter *filehandle* (sb-posix:open name flags))
  (defparameter *filehandle* (sb-posix:open name flags perms)))
;;;-----------------------------
(defparameter *fh* (open path))
#+sbcl
(defparameter *fh* (sb-posix:open path sb-posix:o-rdonly))
;;;-----------------------------
(defparameter *fh* (open path :direction :output))
#+sbcl
(defparameter *fh* (sb-posix:open path (logior sb-posix:o-wronly
                                               sb-posix:o-trunc
                                               sb-posix:o-creat)
                                  #o600))
;;;-----------------------------
#+sbcl
(progn
  (defparameter *fh* (sb-posix:open path (logior sb-posix:o-wronly
                                                 sb-posix:o-excl
                                                 sb-posix:o-creat)))
  (defparameter *fh* (sb-posix:open path (logior sb-posix:o-wronly
                                                 sb-posix:o-excl
                                                 sb-posix:o-creat)
                                    #o600)))
;;;-----------------------------
(defparameter *fh* (open path :direction :output 
                              :if-exists :append
                              :if-does-not-exist :create))
#+sbcl
(progn
  (defparameter *fh* (sb-posix:open path (logior sb-posix:o-wronly
                                                 sb-posix:o-append
                                                 sb-posix:o-creat)))
  (defparameter *fh* (sb-posix:open path (logior sb-posix:o-wronly
                                                 sb-posix:o-append
                                                 sb-posix:o-creat)
                                    #o600)))
;;;-----------------------------
(defparameter *fh* (sb-posix:open path (logior sb-posix:o-wronly sb-posix:o-append)))
;;;-----------------------------
(defparameter *fh* (open path :direction :io :if-exists :overwrite))
#+sbcl
(defparameter *fh* (sb-posix:open path sb-posix:o-rdwr))
;;;-----------------------------
#+sbcl
(progn
  (defparameter *fh* (sb-posix:open path (logior sb-posix:o-rdwr
                                                 sb-posix:o-creat)))
  (defparameter *fh* (sb-posix:open path (logior sb-posix:o-rdwr
                                                 sb-posix:o-creat)
                                    #o600)))
;;;-----------------------------
#+sbcl
(progn
  (defparameter *fh* (sb-posix:open path (logior sb-posix:o-rdwr
                                                 sb-posix:o-excl
                                                 sb-posix:o-creat)))
  (defparameter *fh* (sb-posix:open path (logior sb-posix:o-rdwr
                                                 sb-posix:o-excl
                                                 sb-posix:o-creat)
                                    #o600)))
;;;-----------------------------

Opening Files with Unusual Filenames

;;;-----------------------------
;; The machinations that the Perl example is doing is dealing with the
;; fact that Perl normally ignores leading whitespace in a filename.
;; This shouldn't be necessary in CL (since the filename doesn't also
;; contain the input mode, as it does in Perl), but the following
;; example illustrates how to do the same thing anyway.
(setf *filename* (regex-replace "^(\\s)" *filename* "./$1"))
;; I'm not sure what the \0 being appended in the Perl example is for,
;; but SBCL, at least, doesn't seem to even allow NUL in a namestring
;; (filename), so it's not shown here.
(defparameter *handle* (open *filename*))
;;;-----------------------------
#+sbcl
(defparameter *handle* (sb-posix:open *filename* sb-posix:o-rdonly))
;;;-----------------------------
(defparameter *filename* (second *posix-argv*))
(defparameter *input* (open *filename*))
;;;-----------------------------
(defparameter *output* (open *filename* :direction :output))
;;;-----------------------------
#+sbcl
(defparameter *output* (sb-posix:open *filename* (logior sb-posix:o-wronly 
                                                         sb-posix:o-trunc)))
;;;-----------------------------
(setf *file* (regex-replace "^(\\s)" *file* "./$1"))
(defparameter *output* (open *file* :direction :output))
;;;-----------------------------

Expanding Tildes in Filenames

;;;-----------------------------
;;; @@INCOMPLETE@@
;;; @@INCOMPLETE@@

Making Perl Report Filenames in Errors

;;;-----------------------------

;; You should not normally do this in CL.  However the example below
;; does roughly the same thing as the Perl and is a crude example of
;; how you can handle exceptions in CL.
(handler-case
    (let ((file (open *path*)))
      ;; use FILE
      )
  ;; Catch "all" exceptions (CONDITION is the base class of all
  ;; "exceptions" in CL).
  (condition (msg)
    (format *error-output* "~&Couldn't open ~A for reading : ~A~%" *path* msg)))
;;;-----------------------------

Creating Temporary Files

Storing Files Inside Your Program Text

;;;-----------------------------
(defparameter *data* "
Your data goes here
")

(loop for line in (split #\Newline *data*)
     do
     (progn
       ;; process the line
       ))
;;;-----------------------------
;; The Perl example here would be the same as the above.
;;;-----------------------------
;; There's no equivalent to how DATA is used here.  E.g., there's no
;; standard way to get the currently executing "script" file.
;;;-----------------------------

Writing a Filter

Modifying a File in Place with Temporary File

Modifying a File in Place with -i Switch

Modifying a File in Place Without a Temporary File

Locking a File

Flushing Output

Reading from Many Filehandles Without Blocking

Doing Non-Blocking I/O

Determining the Number of Bytes to Read

Storing Filehandles in Variables

Caching Open Output Filehandles

Printing to Many Filehandles Simultaneously

;;;-----------------------------
(loop
   for filehandle in *filehandles* ; *FILEHANDLES* is list of STREAM objects
   do (princ stuff-to-print filehandle))
;;;-----------------------------
;;; @@INCOMPLETE@@

Opening and Closing File Descriptors by Number

Copying Filehandles

Program: netlock

Program: lockarea