本章将使用到的库有:
当然,Common Lisp 自身也有相应的操作:
检查文件是否存在
probe-file
$ ln -s /etc/passwd foo
* (probe-file "/etc/passwd")
#p"/etc/passwd"
* (probe-file "foo")
#p"/etc/passwd"
* (probe-file "bar")
NIL
创建目录
[ensure-directories-exist](http://www.lispworks.com/documentation/HyperSpec/Body/f_ensu_1.htm)
(ensure-directories-exist "foo/bar/baz/")
该代码将会创建 foo
,bar
,baz
,注意最后需要有个斜杆(/)。
打开文件
[open](http://www.lispworks.com/documentation/HyperSpec/Body/f_open.htm)
,[close](http://www.lispworks.com/documentation/HyperSpec/Body/f_close.htm)
,通常更推荐使用 [with-open-file](http://www.lispworks.com/documentation/HyperSpec/Body/m_w_open.htm)
。其中 open
需要手动将文件关闭,而with-open-file
会在执行代码完后自动将文件关闭。
(with-open-file (str <_file-spec_>
:direction <_direction_>
:if-exists <_if_exists_>
:if-does-not-exist <_if-does-not-exist_>)
<_your code here_>)
str
:绑定打开的文件的变量<_file-spec_>
:文件名或路径<_direction_>
::input
表示读取文件,:output
表示写入文件,:io
表示同时读写,默认的话为::input
。<_if_exists_>
:当文件存在时的一些操作。如果只是对文件进行读取的话,可以忽略。:error
表示错误信号,为默认。:supersede
表示替换原有的文件,:append
在文件后追加,:rename
重命名文件,nil
将流文件绑定为nil
。<_if-does-not-exist_>
:当文件不存在时,:error
错误信息,:create
创建一个空文件,nil
将流文件绑定为nil
。
with-open-file
还有很选项,详情参见 the CLHS entry for [open](http://www.lispworks.com/documentation/HyperSpec/Body/f_open.htm)
读取文件
将文件内容读入字符串或是列表
(uiop:read-file-string "file.txt")
或
(uiop:read-file-lines "file.txt")
这个可以使用 read-line
和 read-char
来实现,但不是个很好的方案,可以使用如下代码:
(with-output-to-string (out)
(with-open-file (in "/path/to/big/file")
(loop with buffer = (make-array 8192 :element-type 'character)
for n-characters = (read-sequence buffer in)
while (< 0 n-characters)
do (write-sequence buffer out :start 0 :end n-characters))))
以 utf-8 编码格式读取
为了避免 ASCII stream decoding error
错误,可以指定读取文件时的编码格式:
(with-open-file (in "/path/to/big/file"
:external-format :utf-8)
...
设置 SBCL 默认编码为 utf-8
在 ~/.sbclrc
中加入下面这句:
(setf sb-impl::*default-external-format* :utf-8)
如果需要的话,也可以加上
(setf sb-alien::*default-c-string-external-format* :utf-8)
逐行读取
(with-open-file (stream "/etc/passwd")
(do ((line (read-line stream nil)
(read-line stream nil)))
((null line))
(print line)))
(with-open-file (stream "/etc/passwd")
(loop for line = (read-line stream nil 'foo)
until (eq line 'foo)
do (print line)))
逐字读取
(with-open-file (stream "/etc/passwd")
(do ((char (read-char stream nil)
(read-char stream nil)))
((null char))
(print char)))
逐次读取
peek-char
CL-USER> (with-input-from-string (stream "I'm not amused")
(print (read-char stream))
(print (peek-char nil stream))
(print (read-char stream))
(values))
#\I
#\'
#\'
当 peek-char
的第一个参数为 T
时,将会跳过空白符
CL-USER> (with-input-from-string (stream "I'm not amused")
(print (read-char stream))
(print (read-char stream))
(print (read-char stream))
(print (peek-char t stream))
(print (read-char stream))
(print (read-char stream))
(values))
#\I
#\'
#\m
#\n
#\n
#\o
当 peek-char
的第一个参数为字符时,会自动去匹配
CL-USER> (with-input-from-string (stream "I'm not amused")
(print (read-char stream))
(print (peek-char #\a stream))
(print (read-char stream))
(print (read-char stream))
(values))
#\I
#\a
#\a
#\m
CL-USER> (with-input-from-string (stream "I'm not amused")
(print (read-char stream))
(print (peek-char #\d stream))
(print (read-char stream))
(print (peek-char nil stream nil 'the-end))
(values))
#\I
#\d
#\d
THE-END
CL-USER> (with-input-from-string (stream "I'm not amused")
(let ((c (read-char stream)))
(print c)
(unread-char c stream)
(print (read-char stream))
(values)))
#\I
#\I
随机访问文件
file-position
CL-USER> (with-input-from-string (stream "I'm not amused")
(print (file-position stream))
(print (read-char stream))
(print (file-position stream))
(file-position stream 4)
(print (file-position stream))
(print (read-char stream))
(print (file-position stream))
(values))
0
#\I
1
4
#\n
5
写入文件
(with-open-file (f <pathname> :direction :output
:if-exists :supersede
:if-does-not-exist :create)
(write-sequence s f)))
str 库中有个简易的函数
(str:to-file "file.txt" content) ;; with optional options
获取文件后缀名
(pathname-type "~/foo.org") ;; => "org"
获取文件属性
(ql:quickload :osicat)
(use-package :osicat)
(let ((stat (osicat-posix:stat #P"./files.md")))
(osicat-posix:stat-size stat)) ;; => 10629
还有其他的一些方法:
osicat-posix:stat-dev
osicat-posix:stat-gid
osicat-posix:stat-ino
osicat-posix:stat-uid
osicat-posix:stat-mode
osicat-posix:stat-rdev
osicat-posix:stat-size
osicat-posix:stat-atime
osicat-posix:stat-ctime
osicat-posix:stat-mtime
osicat-posix:stat-nlink
osicat-posix:stat-blocks
osicat-posix:stat-blksize
列出文件和目录
(namestring #p"/foo/bar/baz.txt") ==> "/foo/bar/baz.txt"
(directory-namestring #p"/foo/bar/baz.txt") ==> "/foo/bar/"
(file-namestring #p"/foo/bar/baz.txt") ==> "baz.txt"
列出目录中的文件
(uiop:directory-files "./")
列出子目录
(uiop:subdirectories "./")
遍历目录
(defparameter *dirs* nil "All recursive directories.")
(uiop:collect-sub*directories "~/cl-cookbook"
(constantly t)
(constantly t)
(lambda (it) (push it *dirs*)))
(cl-fad:walk-directory "./"
(lambda (name)
(format t "~A~%" name)))
匹配文件
(remove-if-not (lambda (it)
(search "App" (namestring it)))
(uiop:directory-files "./"))
(directory #P"*.jpg")
(directory #P"**/*.png")
修改默认路径
(let ((*default-pathname-defaults* (pathname "/bin/")))
(directory "*sh"))
(#P"/bin/zsh" #P"/bin/tcsh" #P"/bin/sh" #P"/bin/ksh" #P"/bin/csh" #P"/bin/bash")