Replace regex in file, in-place, with Common Lisp
I'm trying to write a Common Lisp version of Python regex search and replace using in-place file modification:
import fileinput, re
for line in fileinput.input(inplace=1, backup='.bak'):
line = re.sub(r"foo", "bar", line, re.M)
print (line)
This is the generic Lisp code I was able to come up with:
(require :cl-ppcre)
(defun in-place-subst (file)
(with-open-file (stream file :direction :io :if-exists :overwrite)
(loop for line = (read-line stream nil)
while line do
(write-line (cl-ppcre:regex-replace-all "foo" line "bar") stream))))
It works like. Currently, the replaced text will be appended to the end of the file. My immediate problem is that I cannot figure out how to replace the content.
To better explain if it file.txt
contains:
1 foo
2 bar
3 foobar
after calling
(in-place-subst "file.txt")
I get:
1 foo
2 bar
3 foobar
1 bar
2 bar
3 barbar
Instead of correct replacement:
1 bar
2 bar
3 barbar
I tried with all possible options with-open-file
(from Successful Lisp ), with no success:
Keyword Value Action if File Exists
---------- ------------------ ---------------------------------------
:IF-EXISTS NIL return NIL
:IF-EXISTS :ERROR signal an error
:IF-EXISTS :NEW-VERSION next version (or error)
:IF-EXISTS :RENAME rename existing, create new
:IF-EXISTS :SUPERSEDE replace file upon CLOSE
:IF-EXISTS :RENAME-AND-DELETE rename and delete existing, create new
:IF-EXISTS :OVERWRITE reuse existing file (position at start)
:IF-EXISTS :APPEND reuse existing file (position at end)
Can anyone please send me in the right direction for the function to render in the file.txt
correct way?
Also, what would a normal Lisp idiomatic way of doing this be, assuming of course cl-ppcre
?
Is there a more concise way to do in-place regex replacement using Common Lisp?
source to share
There is no primitive operation in Python that modifies a file in place; instead, there is a helper function fileinput
that gives the illusion of modifying the file in place by first copying the file to the backup file and then reading the backup file and writing the result back to the original. From the manual :
Additional in-place filtering: if a keyword argument is passed
inplace=1
beforefileinput.input()
or to a constructorfileinput
, the file is moved to the backup file and standard output to the input file (if a file with the same name as the backup file already exists, it will be silently replaced) ... This allows you to write a filter that overwrites its input file in place. If the backup option is given (usually as backup = '.'), It specifies the extension for the backup file, and the backup file stays around; the default extension is ".bak", and it is removed when the output file is closed. In-place filtering is disabled when reading stdin.
So, the way to do this operation in Common Lisp is to mimic
Python code by first copying the file into a backup file, for example using this function my-copy-file
, and then write the following code:
(defun in-place-subst (file)
(let ((backup-file (concatenate 'string file ".bak")))
(my-copy-file file backup-file)
(with-open-file (in-stream backup-file)
(with-open-file (out-stream file :direction :output :if-exists :supersede)
(loop for line = (read-line in-stream nil)
while line do
(write-line (cl-ppcre:regex-replace-all "foo" line "bar") out-stream))))))
source to share