逆引きxyzzy lisp(ファイルアクセス) - circleratio/xyzzy GitHub Wiki

目次

ファイルアクセス

テキストファイルをオープンして内容を出力する

xyzzy (Common Lisp)では,ファイルの入出力にストリームを使う. with-open-file マクロを使うと,open/close をまとめてやってくれるので便利.

(with-open-file (in "c:/temp/test.txt" :direction :input)
  (while (let ((l (read-line in nil)))
	   (print l))))

読み込む長さを指定する

以下の例は test.bin をオープンし,50バイト読み込んで内容を表示する.

(with-open-file (in "c:/temp/test.bin" :encoding :binary)
  (let ((ch))
    (dotimes (n 50)
      (setf ch (read-char in nil))
      (print (char-code ch)))))
  0 
  1 
  2
  ...
  49 
=> nil

ファイルの内容を一度に読み込む

バッファに読み込んでから料理するのがよい.


(let ((f "c:/temp/test.txt"))
  (if (file-exist-p f)
      (find-file f)
    (msgbox "~A is not found." f)))

1行ずつ読み込みを行う

(with-open-file (in "c:/temp/test.txt" :direction :input)
	(let ((l)
				(lines 0)
				(fields 0))
		(while (setf l (read-line in nil))
			(setf lines (+ 1 lines))
			(setf fields (+ (length (split-string l #\,)) fields)))
		(values lines fields)))

テキストファイルの特定の行を読み込む

指定したファイル(filename) の n 行目を取り出す.

(defun pickup-line (filename n)
  (with-open-file (in filename :direction :input)
    (dotimes (x (- n 1))
      (read-line in nil))
    (read-line in nil)))

(pickup-line "c:/temp/test.txt" 5)
=> "eee"

--- test.txt の内容 ---
aaa
bbb
ccc
ddd
eee
fff

一時ファイルを作成する

make-temp-file-name を使う. 関数が呼ばれた時点で空のファイルが作られるので注意.

ファイルが作られる場所やファイル名をオプションで指示することもできる.

(make-temp-file-name)
=> "C:/DOCUME~1/tf/LOCALS~1/Temp/~xyzib1p.tmp"

(make-temp-file-name "test" "log" "c:/temp")
=> "c:/temp/testib2c.log"

固定長レコードを読む

TODO

ファイルに書き込む

ファイルの読み込みと同様に,with-open-file を使う.

(with-open-file (out "c:/temp/test.txt" :direction :output)
  (print "test" out))

出力ファイルと同名のファイルが既にある場合の処理は,if-exists を使って指示する.

(with-open-file (out "c:/temp/test.txt" :direction :output :if-exists :error)
  (print "test" out))

指定できる動作は次の通り. :error: エラーメッセージを出す :append: 追記 :rename: テンポラリ名をつけて保存 :override, supercede, rename-and-delete, new-version: 上書き

ファイル名を変更する

(rename-file "c:/temp/test" "c:/temp/test2")
=> t

オプションは下記. *:if-exists **:error **:skip: エラーを出力しない.nilを返す. **:overwrite **:newer: ファイルが新しい場合のみ上書きする. *:if-access-denied **:error **:skip **:force: 強制的なファイル名変更を試みる.

ファイルをコピーする

(copy-file "C:/Temp/memo.sexp" "C:/Temp/memo.sexp.bak")
=> t

フィルタ系のコマンドを作成する

バッファの内容をフィルタリングする例については逆引きxyzzy lisp(バッファ)の項を参照.

外部ファイルを読み込んでフィルタをかける場合は下記.

(defun filter-file (filename func)
  (with-open-file (in filename :direction :input)
    (let ((l))
      (while (setf l (read-line in nil))
        (if (funcall func l)
            (print l))))))

(filter-file "c:/temp/test.txt" (lambda (l) (string-match "b" l)))
=> "abc" 
   "bcd" 
   "gab" 
   "abc" 
   "bcd" 
   nil

--- test.txt の内容 ---
abc
bcd
cde
efg
fga
gab
abc
bcd

リスト等のデータをファイルに保存する(永続化)

ANSI Common Lisp 規格では、コンス、シンボル、文字列、数値、構造体、パスネーム、配列は read/write が可能。

ハッシュについては実装依存だけど、xyzzy では保存してくれない(オブジェクトIDが書き出される…)。

(defun dump (obj)
  (with-open-file (stream "c:/temp/memo.sexp" :direction :output :if-exists :supersede)
    (write obj :stream stream)))

(defun restore ()
  (with-open-file (stream "c:/temp/memo.sexp" :direction :input)
    (read stream)))

(dump '(1 2 3 4 5))
=> (1 2 3 4 5)

(setq x (restore))
=> (1 2 3 4 5)

x
=> (1 2 3 4 5)

バイナリファイルの書き出し

(with-open-file (out "c:/temp/test.bin" :direction :output :if-exist :overwrite :encoding :binary)
  (dotimes (n 128)
    (write-char (code-char n) out)))
=> nil

バイナリファイルの読み込み

(with-open-file (in "c:/temp/test.bin" :encoding :binary)
  (let ((ch))
    (while
        (setf ch (read-char in nil))
      (print (char-code ch)))))
  0 
  1 
  2 
  ...
  127 
=> nil

ファイル情報の取り扱い

ファイルタイプを取得する

file-executable-p は正常動作していないようだ.

(file-exist-p "C:/usr/xyzzy-0.2.2.235/xyzzy/xyzzy.wxp")
=> t

(file-directory-p "C:/usr/xyzzy-0.2.2.235/xyzzy/xyzzy.wxp")
=> nil

(file-executable-p "C:/usr/cygwin/setup.exe")
=> nil

ファイルの詳細情報を取得する

get-file-attributes または get-file-info を使う. 後者の方が詳しい情報が得られる.

(get-file-attributes "c:/temp/testib2c.log")
=> 32

get-file-attributes で得られる属性値の意味は次の通り. :readonly: 1 :hidden: 2 :system: 4 :directory: 16 :archive: 32 :compressed: 2048

(get-file-info "c:/temp/testib2c.log")
=> (32 3506168144 0 nil)

get-file-info は「属性,最終更新時間,サイズ,短い名前」のリストを返す. 属性部分は get-file-attributes と同じ.

結果を真偽値で返す関数もある.

(file-readable-p "C:/usr/xyzzy-0.2.2.235/xyzzy/xyzzy.wxp")
=> t

(file-writable-p "C:/usr/xyzzy-0.2.2.235/xyzzy/xyzzy.wxp")
=> t

(file-length "C:/usr/xyzzy-0.2.2.235/xyzzy/xyzzy.wxp")
=> 1778315

(file-write-time "C:/usr/xyzzy-0.2.2.235/xyzzy/xyzzy.wxp")
=> 3483183828

ファイルモードを変更する

TODO

ファイルの所有者とグループを変更する

TODO

ファイルの最終アクセス時刻と最終更新日時を変更する

(set-file-write-time "C:/Temp/memo.sexp" (get-universal-time))
=> t

相対パスから絶対パスを求める

(set-default-directory "c:/temp")
=> t

(truename "../windows")
=> "c:/Windows"

パスの存在を確認する

(valid-path-p "c:/temp")

ファイル名解析

ファイルパスからディレクトリパスを抜き出す

(directory-namestring "c:/temp/test")
=> "c:/temp/"

実際のファイル構成は見ておらず、単にパスに含まれる最後の "/" を使って処理している。 したがって、上記の例では test がディレクトリであっても、取り除かれてしまう。 また、"c:/temp/test" が存在してもしなくても結果には影響しない。

リスト形式で取得するなら pathname-directory を使う。

(pathname-directory "C:/usr/xyzzy/xyzzy.exe")
=> ("usr" "xyzzy")

ファイルパスからファイル名を抜き出す

(file-namestring "C:/usr/xyzzy/xyzzy.exe")
=> "xyzzy.exe"

拡張子を除いた部分だけを取り出すなら pathname-name を使う。

(pathname-name "C:/usr/xyzzy/xyzzy.exe")
=> "xyzzy"

パス名とファイル名を一度に抜き出す

あまり意味はないけれど.

(defun get-directory-file (path)
  (values
   (directory-namestring path)
   (file-namestring path)))

(get-directory-file "C:/Windows/System32/drivers/etc/hosts")
  => "C:/Windows/System32/drivers/etc/"
     "hosts"

拡張子を調べる

(pathname-type "C:/usr/xyzzy/xyzzy.exe")
=> "exe"

ファイル名作成

ファイル名にデフォルトパスを補完する

(namestring "test/test.txt")
=> "C:/usr/xyzzy-0.2.2.235/xyzzy/test/test.txt"
⚠️ **GitHub.com Fallback** ⚠️