跳轉到內容

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:資源分支

[編輯 | 編輯原始碼]

如果您在 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-fileparse 處理檔案大約快 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)

directing output to more than one device

假設您的指令碼接受一個引數,並且您想將輸出寫入一個具有相同名稱但帶有 .out 字尾的檔案。試試這個

(device (open (string ((main-args) 2) ".out") "write"))

(set 'file-contents (read-file ((main-args) 2)))

現在您可以處理檔案的內容並使用 println 語句輸出任何資訊。

loadsave 函式用於從檔案載入 newLISP 原始碼,並將原始碼儲存到檔案。

read-linewrite-line 函式可用於向執行緒以及檔案讀取和寫入行。參見 向執行緒讀取和寫入.

標準輸入輸出

[編輯 | 編輯原始碼]

要從 STDIO(標準輸入)讀取並寫入 STDOUT(標準輸出),請使用 read-lineprintln。例如,以下是一個簡單的過濾器,它將標準輸入轉換為小寫並將其輸出到標準輸出

#!/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
)

有關引數處理的更多示例,請參閱 簡單倒計時器

華夏公益教科書