Lisp之文件系统
学习一门语言,除了基本的控制操作外,文件系统API也是很重要的,最近学习了LISP的文件系统API ,和大家分享下心得。
open
open是很简单的接口,如下所示:
(open "/path/to/file.txt")
open之后,返回文件描述符,或者说是实用common lisp编程提到的stream 流。我们可以调用read-char或者read-line来读取文件的一个字符或者一行。 下面请看一段示例code:
(defun first-line(filename)
(let ((in (open filename )))
(format t "~a~%" (read-line in))
(close in)
)
)
(first-line "month.lisp" )
(first-line "notexist")
我们可以看到我们尝试输出month.lisp文件的第一行和notexist文件的第一行。看下输出的效果:
root@manu:~/code/lisp# clisp first-line.fas (defun leap-year(year) *** - OPEN: File #P"/home/manu/code/lisp/notexist" does not exist root@manu:~/code/lisp#
对于已经存在的文件,我们输出第一行的内容,对于不存在的内容,我们报了一个错,这表明,仅仅调用open,而不判断返回值是不行的。
对于读打开,如果文件不存在,有不想open报错,可以使用if-does-not-exist 关键字选项来指定不同的行为。
-
error :报错,这是默认行为。
-
create :如果不存在,则创建文件
-
NIL :返回NIL 来替代stream 。
下面我们看一个改进版的读取文件的第一行:
(defun first-line(filename)
(let ((in (open filename :if-does-not-exist NIL)))
(when in
(format t "~a~%" (read-line in))
(close in)
)
)
)
(first-line "month.lisp" )
(first-line "notexist")
OK ,这样就不会出现报错了。
写文件
刚才学到的是读打开,对于写打开,我们需要在调用open的时候,执行direction参数为output。
(open “file_w” :direction :output :if-exists :supersede)
上面的code的意思是写打开名为file_w的文件。对于写打开,open会期待文件不存在,对于文件存在的情形,可以通过指定if-exists来指定如何处理
- supersede: 替换
- append : 追加写
- overwirte:覆盖写
- NIL :返回NIL而不是stream
下面展示一段code
(defun write-file (filename content)
(let ((stream (open filename :direction :output
:if-exists :supersede)))
(format stream "~A ~%" content)
(close stream)
)
)
(write-file "file_w" "hello world")
这段代码的输出如下:
root@manu:~/code/lisp# cat file_w hello world root@manu:~/code/lisp#
cat的lisp实现
cat是shell常用的一个指令,他会将文件的内容输出到终端上。 对于open,经常犯的错误是忘记close。这会造成句柄的泄漏。LISP提供了with-open-file这个宏,来保护这种情况。这个宏会确保返回前close stream。
下面看下cat的lisp实现:
(defun pseudo-cat (file)
(with-open-file (str file :direction :input)
(do ( (line (read-line str nil 'eof)
(read-line str nil 'eof)))
((eql line 'eof))
(format t "~A~%" line)
)
)
)
(pseudo-cat "month.lisp")
获取文件的长度
在linux中提供了POSIX接口stat,可以获取文件的信息,比如文件的类型(LINUX下7中type)获取文件的长度。从我这个初学者的角度来看,LISP做的不好,没有这种接口,需要编码产生。 LISP中提供了一个file-length的接口,可以获取文件的长度,可惜的是,入参是stream,而不是pathname。 我下面封装了一个获取文件长度的接口:
(defun file-len (filename)
(with-open-file (in filename :element-type '(unsigned-byte 8))
(file-length in )
)
)
(format t "file len is ~A ~%" (file-len "month.lisp"))
输出如下:
root@manu:~/code/lisp# clisp file-len.fas file len is 503 root@manu:~/code/lisp#
其他接口
LISP提供了删除的接口delete-file,还有rename-file的重命名接口,比较简单,我就不多说了。
总体来说,LISP的文件操作很奇怪,我学习过C的文件操作,python的和C的几乎一样,可是lisp可能是由于出世太早,看不到posix标准的影子。
参考文献
1 实用Common Lisp编程
2 ANSI COMMON LISP中文版