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
)
有關引數處理的更多示例,請參閱 簡單倒計時器。
