本章将使用到的库有:
当然,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#\dTHE-ENDCL-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#\I14#\n5
写入文件
(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-devosicat-posix:stat-gidosicat-posix:stat-inoosicat-posix:stat-uidosicat-posix:stat-modeosicat-posix:stat-rdevosicat-posix:stat-sizeosicat-posix:stat-atimeosicat-posix:stat-ctimeosicat-posix:stat-mtimeosicat-posix:stat-nlinkosicat-posix:stat-blocksosicat-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")
