LISP: How to read content from a file and write it to another file?
I want to write a function that takes the names of two files as arguments and copies the contents from the first file to the second.
So far I've written a function that reads from a file:
(defun readFile (name)
(let ((in (open name)))
(format t "~a~%" (read-line in))
(close in)))
And the function that writes the string to the file:
(defun writeFile (name content)
(with-open-file (stream name
:direction :output
:if-exists :overwrite
:if-does-not-exist :create)
(format stream content)))
Following Savantes' instructions, I wrote the function again and it looks like this:
(defun read-write-to-file (input-file output-file)
(WITH-OPEN-FILE (output-stream output-file
:direction :output
:if-exists :new-version
:if-does-not-exist :create)
(WITH-OPEN-FILE (input-stream input-file
:direction :input)
(FORMAT output-stream "~a" (READ input-stream nil 'eof))
)))
Now the only problem is that it doesn't read the whole file.
source to share
You have found with-open-file
. Use it for input and output. Don't use open
and close
instead.
Open both files, then write
into the output stream that you are read-line
from the input stream.
Yours WRITEFILE
obviously doesn't compile by the way.
Also, use correct names and indentations.
EDIT after updating the question:
read-line
reads one line. You can write it down with write-line
to preserve line endings. You need to do this in a loop to copy more lines.
Hint: you must write your names in lower case code. The reader closes them automatically. I decided to write WRITEFILE
above to show how your chosen name is handled internally. Capitalization doesn't matter. L
and L
match. Name parts are conditionally hyphenated in Lisp.
source to share
The Common Lisp Cookbook does indeed answer your question:
http://cl-cookbook.sourceforge.net/io.html
See the Bulk I / O section at the bottom of this page.
With minor fixes and modifications, the code will look like this:
(defun my-copy-file (from-file to-file)
(with-open-file (input-stream from-file
:direction :input
:element-type '(unsigned-byte 8))
(with-open-file (output-stream to-file
:direction :output
:if-exists :supersede
:if-does-not-exist :create
:element-type '(unsigned-byte 8))
(let ((buf (make-array 4096 :element-type (stream-element-type input-stream))))
(loop for pos = (read-sequence buf input-stream)
while (plusp pos)
do (write-sequence buf output-stream :end pos))))))
It should be able to handle text files as well as binaries.
source to share
Possibly a more concise solution, at least as fast as above, use copy-stream-to-stream from the excellent UIOP included in ASDF (and therefore with Quicklisp):
(require 'uiop)
(defun file-copy (source destination)
(with-open-file (in source :direction :input)
(with-open-file (out destination :direction :output)
(uiop:copy-stream-to-stream in out))))
It should be as fast (if not more) as WRITE-SEQUENCE is the default. See the first link for more options copy-stream-to-stream
.
source to share
First: Don't use 'READ' for this purpose, use 'READ-LINE'. "READ" performs read-macros, which opens a security hole. Conversely, you need to use "WRITE-LINE".
When you use "READ-LINE" you should see that it does exactly that: it reads exactly one line and returns that or (in your case) the character "EOF" when there is no line to read from the left. It also advances your read pointer after the next "EOL" (or something like that, I'm not really into the implementation specifics of streams), so when you do the next "READ-LINE" on that stream, the next line is read. Now you just have to read and write repeatedly until you hit the end of the file.
source to share