第20章「ファイル」

概要

ANSI Common Lispの第20章「ファイル」を説明します。

ファイルの概要

ANSI Common Lispの第20章はファイルについてかかれてありますが、実はこの章に分類されるオペレータは第19章「ファイルネーム」よりも少なく、例外処理用のものを除けば8つしかありません。また、パスネームは独立したオブジェクトとして扱えますが、ファイル自体は独自のオブジェクトが存在しません。そのため、第7章「オブジェクト」から第21章「ストリーム」までは基本的にデータの構造に関する章ですが、この第20章にはデータ構造に関する System Class の項目がありません。

ファイルの読み取りや書き込みは第22章のストリームが担当しており、ストリームに関するオペレータは充実していますが、そもそもファイルそのものに関する操作は最終的にはオペレーティングシステムの権限に支配されており、プログラムの中から操作するためにはOSと融合している必要があります。Perlが便利なのは、OS(Unix)の機能の一部を簡単な関数で使うことができるからです。Common Lispは残念ながらOSと融合しているとは言い難く、ANSI Common Lispの範囲で一般的なファイル操作を行うことはできません。(例えば、ファイルのコピーを行う関数はありませんので、ライブラリを使うか、自分で定義するしかありません。)

そのため、ファイル操作を行う場合は第19章でも紹介したCL-FADなどのサードパーティ製ライブラリを使うことが一般的です。

このページでは非常に限定的ですが、ANSI Common Lispの範囲のオペレータのみを簡単に紹介します。

ファイル・ディレクトリの存在

ファイルやディレクトリの存在を確認したり、作成したりするには3つの関数が用意されています。

まず、truename関数はパスネームからファイルやディレクトリの完全な名前を含むパスネームに変換します。例えば、多くの処理系ではカレントディレクトリまたは処理系を起動したディレクトリを取得する際にtruename関数を使うことができます。
(truename "./")
; => #P"/Users/satoshi/lisp/"

この関数はファイルやディレクトリが存在しなければエラーを通知するため、ファイルの存在を確認せずに使う場合は留意してください。ファイルが存在するかどうか分からない場合はprobe-file関数を使用してください。
(probe-file "test.lisp")
; => NIL
probe-file関数の返り値がnilの場合は、ファイルが存在しなかったことを意味します。ファイルが存在する場合は、ファイルの完全なパスネームが返り値となります。
(probe-file "markdown.lisp")
; => #P"/Users/satoshi/lisp/markdown.lisp"

ファイルやディレクトリの存在に関するオペレータの最後はensure-directories-exists関数です。これはパスネームに対応するディレクトリが確実に存在するように、もし存在しなければディレクトリを作成します。返り値は多値で、1番目の返り値は引数のパスネーム、2番目の返り値が作成されたか否かを示す真偽値です。
(ensure-directories-exist #p"foo/bar/")
; => #P"foo/bar/" ;
;    T

ディレクトリを作成する場合は、階層的に遡って存在します。例えば、上の例では foo/bar というディレクトリがない状態だったのですが、その上の foo もない状態でした。その場合、foofoo/bar の2つのディレクトリを作成しました。

ファイルの属性

ファイルの属性に関するオペレータは2つしか定められていません。file-author関数とfile-write-date関数です。
file-author関数はSBCLとCCLでは編集者ではなく所有者を返します。Unixでは所有者と所属グループに関する情報はありますが、最後に修正した時のユーザーなどの情報は一般に含まれていません。そのため、所有者を代わりに返します。GNU CLISPではfile-auther関数は常にnilを返す仕様になっており、os:file-owner関数を代わりに使用するよう、マニュアルに書いてあります。
(file-author "markdown.lisp")
; => NIL
(os:file-owner "markdown.lisp")
; => "satoshi"
file-write-date関数は編集日時をユニバーサルタイムで返します。
(file-write-date "markdown.lisp")
; => 3718697377
(decode-universal-time (file-write-date "markdown.lisp"))
; => 37 ;
;    29 ;
;    20 ;
;    3 ;
;    11 ;
;    2017 ;
;    4 ;
;    NIL ;
;    -9

2017年11月3日 20時29分37秒が最終更新日時であることが分かります。

ファイル・ディレクトリの操作

最後に、ファイルやディレクトリの操作を行う関数を3つ紹介します。

まずは、ファイル名の変更を行うrename-file関数です。
(rename-file "test.lisp" "test2.lisp")
; => #P"test2.lisp" ;
;    #P"/Users/satoshi/Desktop/test.lisp" ;
;    #P"/Users/satoshi/Desktop/test2.lisp"

返り値は多値で、1番目は新しいファイル名の一般的なパスネーム、2番目が古いファイル名の完全なパスネーム、3番目が新しいファイル名の完全なパスネームです。

次は、ファイルの削除を行うdelete-file関数です。この関数はGNU CLISPではANSI Common Lispに定められた仕様と異なる動作をするので注意してください。

ANSI Common Lispの仕様では、この関数は常にtを返すことになっています。ファイルが存在しない場合や削除できない場合はエラーが通知され、例外的状況に入るので、通常の状況における返り値は常にtです。しかし、GNU CLISPではファイルが存在しない場合はnilを返し、ファイルが存在して削除できた場合は削除したファイルの完全なパスネームを返します。
;; SBCL, CCLなどではエラーになる
(delete-file "test.lisp")
; => NIL

;; SBCL, CCLなどでは t が返り値となる
(delete-file "test2.lisp")
; => #P"/Users/satoshi/Desktop/test2.lisp"

複数の処理系を使用する場合は注意してください。

最後に紹介するオペレータはdirectory関数です。この関数はあるパスネームで示されるディレクトリの中のファイルを全て取得して、リストにして返します。
(directory "./*")
; => (#P"/Users/satoshi/lisp/markdown.lisp" 
;     #P"/Users/satoshi/lisp/markdown.lib"
;     #P"/Users/satoshi/lisp/markdown.fas" 
;     #P"/Users/satoshi/lisp/markdown.dx64fsl"
;     #P"/Users/satoshi/lisp/asdf.lisp"
;     #P"/Users/satoshi/lisp/asdf.lib"
;     #P"/Users/satoshi/lisp/asdf.fas")

ただし、この関数も基本的には処理系依存ですので、複数の処理系を使用する場合は十分に注意してください。

0 件のコメント :

コメントを投稿