newLISP 简介/文件操作
与文件操作相关的函数可以分为两类:与操作系统交互,以及从文件读写数据。
newLISP 中维护着当前工作目录的概念。当您在终端中输入 newLISP 启动 newLISP 时,您的当前工作目录将变为 newLISP 的当前目录。
$ pwd /Users/me/projects/programming/lisp $ newlisp newLISP v.9.3 on OSX UTF-8, execute 'newlisp -h' for more info. > (env "PWD") "/Users/me/projects/programming/lisp" > (exit) $ pwd /Users/me/projects/programming/lisp $
您也可以使用没有参数的 real-path 函数检查您的当前工作目录
(real-path)
;-> "/Users/me/projects/programming/lisp"
但是,当您从其他地方(例如文本编辑器内部)运行 newLISP 脚本时,当前工作目录和其他设置可能会有所不同。因此,如果您不确定,最好使用 change-dir 来建立当前工作目录
(change-dir "/Users/me/Documents")
;-> true
其他环境变量可以使用 env 访问
(env "HOME")
;-> "/Users/me"
(env "USER")
;-> "cormullion"
同样,从文本编辑器而不是在交互式终端会话中运行 newLISP 会影响哪些环境变量可用以及它们的值。
获得正确的目录后,使用 directory 列出其内容
(directory)
;-> ("." ".." ".bash_history" ".bash_profile" ".inputrc" ".lpoptions"
; ".sqlite_history" ".ssh" ".subversion" "bin" "Desktop" "Desktop Folder"
; "Documents" "Library" ...
等等。请注意,它为您提供的是相对文件名,而不是绝对路径名。 directory 也可以列出除当前工作目录之外的目录的内容,只要您提供路径即可。在这种扩展形式中,您可以使用正则表达式来过滤内容
(directory "./") ; just a pathname
;-> ("." ".." ".bash_history" ".bash_profile" ".inputrc" ".lpoptions"
; ".sqlite_history" ".ssh" ".subversion" "bin" "Desktop" "Desktop Folder"
; "Documents" "Library" ...
(directory "./" {^[^.]}) ; exclude files starting "."
;-> ("bin" "Desktop" "Desktop Folder" "Documents" "Library" ... )
同样,请注意结果相对于当前工作目录。存储您列出的目录的路径通常很有用,以防您以后需要使用它来构建完整路径名。 real-path 返回文件或目录的完整路径名,无论是在当前工作目录中
(real-path ".subversion")
;-> "/Users/me/.subversion"
还是由另一个相对路径名指定
(real-path "projects/programming/lisp/lex.lsp")
;-> "/Users/me/projects/programming/lisp/lex.lsp"
要查找磁盘上项目包含的目录,您只需从完整路径名中删除文件名即可
(set 'f "lex.lsp")
(replace f (real-path f) "")
;-> "/Users/me/projects/programming/lisp/"
顺便说一下,如果文件名在路径中更早的位置也作为目录名出现,则此方法并不总是有效。一个简单的解决方案是使用 $ 选项对仅出现在路径名末尾的 f 进行正则表达式搜索
(replace (string f "\$") (real-path f) "" 0)
要递归地扫描文件系统的某个部分,请使用一个递归调用自身的函数。这里只打印完整路径名
(define (search-tree dir)
(dolist (item (directory dir {^[^.]}))
(if (directory? (append dir item))
; search the directory
(search-tree (append dir item "/"))
; or process the file
(println (append dir item)))))
(search-tree {/usr/share/newlisp/})
/usr/share/newlisp/guiserver/allfonts-demo.lsp /usr/share/newlisp/guiserver/animation-demo.lsp ... /usr/share/newlisp/util/newlisp.vim /usr/share/newlisp/util/syntax.cgi
另见 编辑文件夹和层次结构中的文本文件.
您会发现一些测试函数很有用
- file? 这个文件或目录存在吗?
- directory? 这个路径名是目录还是文件?
记住相对路径名和绝对路径名之间的区别
(file? "System")
;-> nil
(file? "/System")
;-> true
您可以使用 file-info 获取有关文件的信息。此函数询问操作系统有关文件的信息,并将信息以一系列数字的形式返回
- 0 是大小
- 1 是模式
- 2 是设备模式
- 3 是用户 ID
- 4 是组 ID
- 5 是访问时间
- 6 是修改时间
- 7 是状态更改时间
例如,要找出文件的大小,请查看 file-info 返回的第一个数字。以下代码列出了目录中的文件,并包括它们的大小。
(set 'dir {/usr/share/newlisp/modules})
(dolist (i (directory dir {^[^.]}))
(set 'item (string dir "/" i))
(if (not (directory? item))
(println (format {%7d %-30s} (nth 0 (file-info item)) item))))
35935 /usr/share/newlisp/modules/canvas.lsp 6548 /usr/share/newlisp/modules/cgi.lsp 5460 /usr/share/newlisp/modules/crypto.lsp 4577 /usr/share/newlisp/modules/ftp.lsp 16310 /usr/share/newlisp/modules/gmp.lsp 4273 /usr/share/newlisp/modules/infix.lsp 12973 /usr/share/newlisp/modules/mysql.lsp 16606 /usr/share/newlisp/modules/odbc.lsp 9865 /usr/share/newlisp/modules/pop3.lsp 12835 /usr/share/newlisp/modules/postgres.lsp 31416 /usr/share/newlisp/modules/postscript.lsp 4337 /usr/share/newlisp/modules/smtp.lsp 10433 /usr/share/newlisp/modules/smtpx.lsp 16955 /usr/share/newlisp/modules/sqlite3.lsp 21807 /usr/share/newlisp/modules/stat.lsp 7898 /usr/share/newlisp/modules/unix.lsp 6979 /usr/share/newlisp/modules/xmlrpc-client.lsp 3366 /usr/share/newlisp/modules/zlib.lsp
请注意,我们将目录的名称存储在 dir 中。 directory 函数返回相对文件名,但您必须将绝对路径名字符串传递给 file-info,除非字符串引用的是当前工作目录中的文件。
您可以使用隐式寻址来选择所需的项目。因此,不是(nth 0 (file-info item)),您可以写(file-info item 0).
如果您在 MacOS X 上尝试了之前的脚本,您可能会注意到有些文件的大小为 0 字节。这可能表明存在从经典(即旧)Macintosh 时代继承的双分支系统。使用以下版本访问文件的资源分支。字体文件夹是日益罕见的资源分支的绝佳搜寻地
(set 'dir "/Users/me/Library/Fonts") ; fonts folder
(dolist (i (directory dir "^[^.]"))
(set 'item (string dir "/" i))
(and
(not (directory? item)) ; don't do folders
(println
(format "%9d DF %-30s" (nth 0 (file-info item)) item))
(file? (format "%s/..namedfork/rsrc" item)) ; there's a resource fork too
(println (format "%9d RF"
(first (file-info (format "%s/..namedfork/rsrc" item)))))))
... 0 DF /Users/me/Library/Fonts/AvantGarBoo 26917 RF 0 DF /Users/me/Library/Fonts/AvantGarBooObl 34982 RF 0 DF /Users/me/Library/Fonts/AvantGarDem 27735 RF 0 DF /Users/me/Library/Fonts/AvantGarDemObl 35859 RF 0 DF /Users/me/Library/Fonts/ITC Avant Garde Gothic 1 116262 RF ...
要管理文件,您可以使用以下函数
- rename-file 重命名文件或目录
- copy-file 复制文件
- delete-file 删除文件
- make-dir 创建新目录
- remove-dir 删除空目录
例如,要重新编号当前工作目录中的所有文件,以便文件按修改日期排序,您可以编写类似以下内容
(set 'dir {/Users/me/temp/})
(dolist (i (directory dir {^[^.]}))
(set 'item (string dir "/" i))
(set 'mod-date (date (file-info item 6) 0 "%Y%m%d-%H%M%S"))
(rename-file item (string dir "/" mod-date i)))
;-> before image-001.png image-002.png image-003.png image-004.png ;-> after 20061116-120534image-001.png 20061116-155127image-002.png 20061117-210447image-003.png 20061118-143510image-004.png
的(file-info item 6)提取 file-info 返回的结果的修改时间(项目 6)。
在将此类脚本用于实际工作之前,请始终对其进行测试!一个错误的标点符号可能会造成严重破坏。
newLISP 有一个很好的输入和输出函数选择。
将文本写入文件的简单方法是 append-file,它将字符串添加到文件的末尾。如果文件不存在,则会创建它。它非常适合创建日志文件和定期写入的文件
(dotimes (x 10)
(append-file "/Users/me/Desktop/log.log"
(string (date) " logging " x "\n")))
现在我的桌面上有一个文件,其内容如下
Sat Sep 26 09:06:08 2009 logging 0 Sat Sep 26 09:06:08 2009 logging 1 Sat Sep 26 09:06:08 2009 logging 2 Sat Sep 26 09:06:08 2009 logging 3 Sat Sep 26 09:06:08 2009 logging 4 Sat Sep 26 09:06:08 2009 logging 5 Sat Sep 26 09:06:08 2009 logging 6 Sat Sep 26 09:06:08 2009 logging 7 Sat Sep 26 09:06:08 2009 logging 8 Sat Sep 26 09:06:08 2009 logging 9
您不必担心打开和关闭文件。
要将文件的内容一次性加载到符号中,请使用 read-file。
(set 'contents (read-file "/usr/share/newlisp/init.lsp.example"))
;->
";; init.lsp - newLISP initialization file\n;; gets loaded automatically on
; ...
(load (append $HOME \"/.init.lsp\")) 'error))\n\n;;;; end of file ;;;;\n\n\n "
符号 contents 将文件的内容存储为单个字符串。
open 返回一个值,该值充当对文件的引用或“句柄”。您可能希望稍后使用该文件,因此请将引用存储在符号中
(set 'data-file (open "newfile.data" "read")) ; in current directory
; and later
(close data-file)
使用 read-line 从文件句柄中逐行读取文件。每次使用 read-line 时,下一行将存储在缓冲区中,您可以使用 current-line 函数访问它。读取文件的基本方法如下
(set 'file (open ((main-args) 2) "read")) ; the argument to the script
(while (read-line file)
(println (current-line))) ; just output the line
read-line 会丢弃每行末尾的换行符。 println 会在您提供的文本末尾添加一个换行符。有关参数处理和 main-args 的更多信息,请参见 STDIO.
对于中小型文件,逐行读取源文件比将整个文件一次性加载到内存中要慢得多。例如,本书的源文档大约有 6000 行文本,大约 350KBytes。使用 read-file 和 parse 处理文件大约快 10 倍,如下所示
(set 'source-text (read-file "/Users/me/introduction.txt"))
(dolist (ln (parse source-text "\n" 0))
(process-line ln))
而不是使用 read-line,如下所示
(set 'source-file (open "/Users/me/introduction.txt" "read"))
(while (read-line source-file)
(process-line (current-line)))
device 函数是将输出在控制台和文件之间切换的便捷方法
(set 'output-file (open "/tmp/file.txt" "write"))
(println "1: this goes to the console")
(device output-file)
(println "2: this goes to the temp file")
(device 0)
(println "3: this goes to the console")
(close output-file)
假设您的脚本接受一个参数,并且您想将输出写入一个具有相同名称但带有 .out 后缀的文件。试试这个
(device (open (string ((main-args) 2) ".out") "write"))
(set 'file-contents (read-file ((main-args) 2)))
现在您可以处理文件的内容并使用 println 语句输出任何信息。
load 和 save 函数用于从文件加载 newLISP 源代码,并将源代码保存到文件。
read-line 和 write-line 函数可用于向线程以及文件读取和写入行。参见 向线程读取和写入.
要从 STDIO(标准输入)读取并写入 STDOUT(标准输出),请使用 read-line 和 println。例如,以下是一个简单的过滤器,它将标准输入转换为小写并将其输出到标准输出
#!/usr/bin/newlisp
(while (read-line)
(println (lower-case (current-line))))
(exit)
它可以在 shell 中这样运行
$ ./lower-case.lsp HI hi HI THERE hi there ...
以下简短的脚本是由用户 Echeam 提交到用户论坛的有用 newLISP 格式化程序
#!/usr/bin/env newlisp
(set 'indent " ")
(set 'level 0)
(while (read-line)
(if (< level 0) (println "ERROR! Too many close-parenthesis. " level))
(letn ((ln (trim (current-line))) (fc (first ln)))
(if (!= fc ")") (println (dup indent level) ln)) ; (indent & print
(if (and (!= fc ";") (!= fc "#")) ; don't count if line starts with ; or #
(set 'level
(+ level (apply - (count (explode "()") (explode (current-line)))))))
(if (= fc ")") (println (dup indent level) ln))) ; (dedent if close-parenthesis
)
(if (!= level 0) (println "ERROR! Parenthesis not balanced. " level))
(exit)
你可以从命令行运行它,方法是
$ format.lsp < inputfile.lsp
要在命令行使用 newLISP 程序,可以使用 main-args 函数访问参数。例如,如果你创建了这个文件
#!/usr/bin/newlisp
(println (main-args))
(exit)
使其可执行,然后在 shell 中运行它,你将看到在运行时提供给脚本的参数列表
$ test.lsp 1 2 3 ("/usr/bin/newlisp" "/Users/me/bin/test.lsp" "1" "2" "3") $
main-args 返回传递给程序的参数列表。前两个参数(你可能不想处理它们)分别是 newLISP 程序的路径和正在执行的脚本的路径名
(main-args)
;-> ("/usr/bin/newlisp" "/path/script.lsp" "1" "2" "3")
因此,你可能想要从索引 2 开始处理参数
((main-args) 2)
;-> 1
(main-args 2) ; slightly simpler
;-> 1
它以字符串形式返回。或者,要处理从索引 2 开始的所有参数,请使用切片
(2 (main-args))
;-> ("1" "2" "3")
参数以字符串列表的形式返回。
通常,你希望在脚本中遍历所有主参数:一个方便的短语是
(dolist (a (2 (main-args)))
(println a))
另一个更具可读性的等效方法是,它遍历其余参数
(dolist (a (rest (rest (main-args))))
(println a))
以下是一个简短的脚本,它从文本文件中过滤掉不需要的 Unicode 字符,但允许一些特殊的字符通过
(set 'file (open ((main-args) 2) "read")) ; one file
(define (in-range? n low high)
; is n between low and high inclusive?
(and (<= n high) (>= n low)))
(while (read-line file)
(dostring (c (current-line))
(if
(or
(in-range? c 32 127) ; ascii
(in-range? c 9 10) ; tab newline
(in-range? c 12 13) ; \f \r
(= c (int "\0xbb")) ; right double angle
(= c (int "\0x25ca")) ; diamond
(= c (int "\0x2022")) ; bullet
(= c (int "\0x201c")) ; open double quote
(= c (int "\0x201d")) ; close double quote
)
(print (char c)))) ; nothing to do
(println) ; because read-line swallows line endings
)
有关参数处理的更多示例,请参阅 简单倒计时器。