読者です 読者をやめる 読者になる 読者になる

opamp_sando's blog

クソザコが割りと適当なことを書くためにある備忘録です。あとたまに普通の日記も書きます

Common Lispのパスネームまわりについて

Common Lisp(CL)のパスネームに関してのメモ。このブログでCommon Lispの記事が初めてということから察せると思いますが、私は最近EmacsからLispに興味を持ってLispを使い始めたばかりのただのカカシなので、解釈が間違えているところがあるかもしれないです。
このメモの内容は最後の参考書籍を元にまとめたものです。

※処理系はClozure CL

パスネーム(Pathname)とは

プログラム中でファイルを扱う場合は、当然対象のファイルの場所(PATH)を何かしらの方法で指定する。Common Lispでは文字列を使って指定することもできるが、パスネームという特別なオブジェクトも用意されている。パスネームはOSやFileSystemによる違いを吸収するために存在する。パスネームはホスト、デバイス、ディレクトリ、ネーム、タイプ、バージョンの要素を持っている。このうち殆どは文字列として表現されるが、ディレクトリだけは:absoluteか:relativeキーワードを戦闘に持つディレクトリ名のリストである。また、これらの要素はすべて必ず使われるわけではない。例えば、WindowsにはCドライブなどの概念が存在するので、その情報を保持するためにデバイスやホスト要素が使われる。UNIXではディレクトリとネームとタイプが使われる。

Common Lispでは文字列、ストリーム、パスネームオブジェクトを使ってファイルを指定することができる。これらはまとめてパスネーム指示子(Pathname designator)と呼ばれる。

PATHENAME関数

PATHNAME関数はパスネーム指示子を引数にとり、等価なパスネームオブジェクトを返す。

(pathname "/home/opamp/tmp/hoge.txt")) ; 文字列をパスネーム指定子として渡した例

これによって、パスネーム指定子は適切にパースされ、パスネームオブジェクトが返る。
パスネームオブジェクトの要素は以下のようにして、PATHNAME-DIRECTORYなどの関数で調べることができる。

(setf p (pathname "/home/opamp/tmp/hoge.txt")) ; => #P"/home/opamp/tmp/hoge.txt"
(pathname-directory p) ; => (:ABSOLUTE "home" "opamp" "tmp")
(pathname-name p) ; => "hoge"
(pathname-type p) ; => "txt"

Windowsの場合は

(pathname-device (pathname "C:\Users\opamp\hoge.txt")) ; => "C"

のように、デバイス要素にも値が入る。

また、パスネームは以下のようにシンタックスを用いて記述することもできる。

(pathname-directory #p"/home/opamp/tmp/hoge.txt")

パスネームから文字列へ

パスネームオブジェクトがあり、表示のためなどの理由から文字列に変換したい場合は、NAMESTRINGやDIRECTORY-NAMESTRING、FILE-NAMESTRINGが使える。

例:

(namestring #p"/var/log/pacman.log") ; => "/var/log/pacman.log"
(directory-namestring #p"/var/log/pacman.log") ; => "/var/log/"
(file-namestring #p"/var/log/pacman.log") ; => "pacman.log"

MAKE-PATHNAME関数

MAKE-PATHNAME関数を使うと、パスネームの構築ができる。この関数はパスネームオブジェクトの要素名をキーワードとしたキーワード引数で各要素の値を渡す。おそらく例を見たほうが早いので、先ほどの"/var/log/pacman.log"のパスネームオブジェクトをMAKE-PATHNAMEで作成してみる。

(make-pathname
  :directory '(:absolute "var" "log")
  :name "pacman"
  :type "log")

これで返ったパスネームオブジェクトにnamestringをすると"/var/log/pacman.log"となり、ただしく構築できていることがわかる。
当然、:deviceなどのキーワードを使うこともできる。

また、次のような使い方もできる。

(make-pathname 
  :type "log"
  :defaults "/var/log/pacman") ; => #p"/var/log/pacman.log"

defaultsに指定されたパスネームを基にして新しいパスネームを構築できる。

MERGE-PATHNAMES関数

これは2つのパスネームをマージする関数である。具体的には、1つ目のパスネーム中のNILな要素を2つ目のパスネームの値で補完する。
ただし、この関数はディレクトリ要素に関して特別な挙動をし、1つ目のディレクトリ要素が相対指定だった場合、マージ後の(新しく作られる)パスネームオブジェクトのディレクトリ要素は、2つ目のディレクトリ要素を相対PATHの起点として使用する。

例:

(merge-pathnames #p"log/pacman.log" #p"/var/") ; => #P"/var/log/pacman.log"

ちなみに、ENOUGH-NAMESTRINGを使うと、逆に相対PATHを得ることができる。

(enough-namestring #p"/var/log/pacman.log" #p"/var/") ; => "log/pacman.log"

また、例えばタイプとネームしかないような不完全なパスネームがあるとする。このパスネームからはファイル名はわかるが、どのディレクトリに含まれるものなのかがわからない。このように情報がかけている場合、Common LispDEFAULT-PATHNAME-DEFAULTSという値から情報を補完しようとする。この値は処理系によって異なる。
MERGE-PATHNAMEは引数1つで呼ぶことができ、その場合は2つ目の引数としてこのDEFAULT-PATHNAME-DEFAULTSが使われる。

ディレクトリ名のパスネーム

ディレクトリをパスネームで表現するとき、表現する方法が2つある。1つはファイル形式、もうひとつはディレクトリ形式と呼ばれている。
例えば、以下のディレクトリを例に取る。

/usr/bin/

すると、

;;ファイル形式
(make-pathname 
  :directory '(:absolute "usr") :name "bin") ; file form

;; ディレクトリ形式
(make-pathname
  :directory '(:absolute "usr" "bin")) ; directory form

という2種類の表現ができる。これはUNIXWindowsにおいて、ディレクトリを一種の特殊なファイルとみなせることから来ている。

MAKE-PATHNAMEを使うとわかりやすいが、PATHNAMEを使う場合は注意する必要がある。

;; ファイル形式
(pathname-directory (pathname "/usr/bin")) ; => (:ABSOLUTE "usr")

;; ディレクトリ形式
(pathname-directory (pathname "/usr/bin/")) ; => (:ABSOLUTE "usr" "bin")

ファイルの存在をチェックする

ファイルがあるかどうかチェックしたい場合は、PROBE-FILEを使う。

(unless (probe-file "/var/log/pacman.log")
  (format *error-output* "File not found~%"))

存在するならば、真の名前のパスネームを返す。つまり、シンボリックリンクなどが途中にあっても、それを解決した本当の名前を返す。
存在しなければNILが返る。

その他

RENAME-FILE . 2つのパスネーム指定子をとり、1つ目の名前を2つ目の名前に変える
DELETE-FILE . ファイルを削除する(成功すれば真、失敗すればFILE-ERROR)
ENSURE-DIRECTORIES-EXIST . ディレクトリを作成する(ただしディレクトリ形式で指定)
FILE-WRITE-DATE . 1900年1月1日から経過した秒数という形式でファイルの更新日を返す
FILE-AUTHOR . 所有者を返す
FILE-LENGTH . ストリームを引数にとる。ファイルのバイト長を返す  
少しだけ追記

その他、便利なパスネーム周りの関数を提供してくれるライブラリとして以下のものがある。
CL-FAD - A portable pathname library for Common Lisp
使い方はhttp://sakito.jp/cl/cl-fad.htmlを参照。

ディレクトリ内のファイルをリストで返してくれたり、パスネームのディレクトリ形式、ファイル形式が相互変換できる関数があったりといろいろ便利。

参考書籍

実践Common Lisp

実践Common Lisp

ほとんどこの本のとおりです。丸写しではないけど、内容の一部を端折ってまとめただけです。

Firefox ブラウザ無料ダウンロード