Copy byte-per-byte of a file
The following function copies a file into another by performing an exact byte-per-byte copy, ignoring the kind of content (which can be either lines of characters in some encoding or binary data):
(defun byte-copy (infile outfile)
(with-open-file (instream infile :direction :input :element-type '(unsigned-byte 8)
:if-does-not-exist nil)
(when instream
(with-open-file (outstream outfile :direction :output :element-type '(unsigned-byte 8)
:if-exists :supersede)
(loop for byte = (read-byte instream nil)
while byte
do (write-byte byte outstream))))))
The type (unsigned-byte 8)
is the type of 8-bit bytes. The functions read-byte
and write-byte
work on bytes, instead of read-char
and write-char
that work on characters. read-byte
returns a byte read from the stream, or NIL
at the end of the file if the second optional parameter is NIL
(otherwise it signals an error).
Bulk copy
An exact copy, more efficient the the previous one. can be done by reading and writing the files with large chunks of data each time, instead of single bytes:
(defun bulk-copy (infile outfile)
(with-open-file (instream infile :direction :input :element-type '(unsigned-byte 8)
:if-does-not-exist nil)
(when instream
(with-open-file (outstream outfile :direction :output :element-type '(unsigned-byte 8)
:if-exists :supersede)
(let ((buffer (make-array 8192 :element-type '(unsigned-byte 8))))
(loop for bytes-read = (read-sequence buffer instream)
while (plusp bytes-read)
do (write-sequence buffer outstream :end bytes-read)))))))
read-sequence
and write-sequence
are used here with a buffer which is a vector of bytes (they can operate on sequences of bytes or characters). read-sequence
fills the array with the bytes read each time, and returns the numbers of bytes read (that can be less than the size of the array when the end of file is reached). Note that the array is destructively modified at each iteration.
Exact copy line-per-line of a file
The final example is a copy performed by reading each line of characters of the input file, and writing it to the output file. Note that, since we want an exact copy, we must check if the last line of the input file is terminated or not by an end of line character(s).
For this reason, we use the two values returned by read-line
: a new string containing the characters of the next line, and a boolean value that is true if the line is the last of the file and does not contain the final newline character(s). In this case write-string
is used instead of write-line
, since the former does not add a newline at the end of the line.
(defun line-copy (infile outfile)
(with-open-file (instream infile :direction :input :if-does-not-exist nil)
(when instream
(with-open-file (outstream outfile :direction :output :if-exists :supersede)
(let (line missing-newline-p)
(loop
(multiple-value-setq (line missing-newline-p)
(read-line instream nil nil))
(cond (missing-newline-p ; we are at the end of file
(when line (write-string line outstream)) ; note `write-string`
(return)) ; exit from simple loop
(t (write-line line outstream)))))))))
Note that this program is platform independent, since the newline character(s) (varying in different operating systems) is automatically managed by the read-line
and write-line
functions.