CL-FAD: ファイル・ディレクトリ操作ライブラリ

概要

CL-FADは可搬性のあるファイル・ディレクトリ操作ライブラリです。

ANSI Common Lispの標準仕様では第19章に「ファイルネーム」が、第20章に「ファイル」が定められていますが、処理系・環境依存のものも多く、Windows, macOS, Linuxなど多様なOSで共通して用いることができるオペレータは多くありません。

CL-FADは処理系や環境に依存せず、ポータブルに利用できることを念頭に設計されており、また、便利系のオペレータも実装されていることから、ファイル・ディレクトリ操作のデファクトスタンダードとして広く利用されています。

モジュール管理ツールであるASDFが3に移行してからはファイルネーム関係の環境依存を吸収するためにUIOPが独立して容易されているため、ASDFを使用可能な環境ではUIOPを用いることも可能です。実際、UIOPはCL-FADなど複数のライブラリの代替となることを目指して設計されていますが、そのカバー範囲がかなり広いことやドキュメントが未整備であることなどから、依然CL-FADが広く愛用されているという状況にあります。

CL-FADはANSI Common Lispの第19章・第20章を大幅に補強するものですので、使いたい機能がCL-FADに含まれる場合は積極的に活用してください。

なお、CL-FADはBordeaux ThreadsAlexandriaに依存していますので、あらかじめASDFなどでロードできる状態にしておいてください。このページでは、以下の状態でCL-FADをロード済として説明します。

(require "asdf")
; => T
(asdf:load-system :cl-fad)
; => T

調査に関するオペレータ

この節では、ファイルやディレクトリに関する調査を行うオペレータを紹介します。

directory-exists-p関数: ディレクトリの存在確認

指定されたディレクトリが存在するかどうかを確認します。存在する場合はそのパスを、存在しない場合はnilを返します。
(fad:directory-exists-p #p"common-lisp")
; => #P"common-lisp/"

file-exists-p関数: ファイルの存在確認

指定されたファイルが存在するかどうかを確認します。存在する場合はそのパスを、存在しない場合はnilを返します。
(fad:file-exists-p #p".ccl-init.lisp")
; => #P"/Users/satoshi/.ccl-init.lisp"

directory-pathname-p関数: ディレクトリ指定かどうかの確認

指定されたパスがディレクトリを意味する場合はそのパスを、ディレクトリでない場合はnilを返します。
(fad:directory-pathname-p #p"common-lisp")
; => NIL

(fad:directory-pathname-p #p"common-lisp/")
; => #P"common-lisp/"

pathname-absolute-p関数: 絶対パスかどうかの確認

指定された引数が絶対パスを意味する場合はtを、絶対パスではない場合はnilを返す述語関数です。
(fad:pathname-absolute-p "/Users/satoshi/")
; => T

(fad:pathname-absolute-p "./")
; => NIL

pathname-relative-p関数: 相対パスかどうかの確認

指定された引数が相対パスを意味する場合はtを、相対パスではない場合はnilを返す述語関数です。
(fad:pathname-relative-p "./")
; => T

pathname-root-p関数: ルートディレクトリかどうかの確認

指定された引数がルートを意味する場合はtを、ルートではない場合はnilを返す述語関数です。
(fad:pathname-root-p #p"/")
; => T

(fad:pathname-root-p "/")
; -> T

パスネームの処理に関するオペレータ

この節ではパスネームオブジェクト(ファイルネーム)の処理に関するオペレータを紹介します。

canonical-pathname関数: パスネームの正規化

冗長なパスネームを正規化します。
(fad:canonical-pathname "/a/b/../../a/b/c.txt")
; => #P"/a/b/c.txt"

merge-pathnames-as-directory関数: パスネーム(ディレクトリ)の連結

ディレクトリを意味するパスネームを連結します。ファイルを意味する部分は破棄されます。
(fad:merge-pathnames-as-directory "a/" "b/" "c/d")
; => #P"a/b/c/"

merge-pathnames-as-file関数: パスネーム(ファイル)の連結

パスネームを連結し、ファイルを意味するパスネームを生成します。
(fad:merge-pathnames-as-file "a/" "b/" "c/d")
; => #P"a/b/c/d"

pathname-as-directory関数: ディレクトリを意味するパスネームへの変換

パスネームがディレクトリを意味する場合はそのまま返し、ファイルを意味する場合はディレクトリに変換して返します。
(fad:pathname-as-directory #p"a/b/c")
; => #P"a/b/c/"

pathname-as-file関数: ファイルを意味するパスネームへの変換

パスネームがファイルを意味する場合はそのまま返し、ディレクトリを意味する場合はファイルに変換して返します。
(fad:pathname-as-file #p"a/b/c/")
; => #P"a/b/c"

pathname-directory-pathname関数: ディレクトリ部分のパスネームの取得

pathname-as-directory関数と似ていますが、こちらはファイルを意味するパスネームが指定された場合にディレクトリに変換するのではなく、そのファイルが属するディレクトリ部分のパスネームのみを取得します。
(fad:pathname-directory-pathname #p"a/b/c")
; => #P"a/b/"

pathname-parent-directory関数: 一つ上のディレクトリに存在する場合のパスネーム

この関数の動作は少し複雑です。

引数がディレクトリを意味する場合、そのディレクトリが属する親ディレクトリを取得します。
(fad:pathname-parent-directory #p"a/b/c/")
; => #P"a/b/"

他方、引数がファイルを意味する場合、そのファイルが一つ上のディレクトリに存在したとしたら、という仮定で得られるパスネームを取得します。
(fad:pathname-parent-directory #p"a/b/c")
; => #P"a/c"

この例の場合、cというファイルはbというディレクトリに属しているのが引数の状態ですが、返り値ではbの親ディレクトリであるaに属している場合のパスとなっています。

ディレクトリの探索に関するオペレータ

この節ではディレクトリ内のファイル一覧を取得するなど、探索に関するオペレータを紹介します。

list-directory関数: ディレクトリ内の一覧取得

指定されたディレクトリ直下に属するファイルやディレクトリなどのパスネームをまとめてリストとして返す関数です。ディレクトリを再帰的に探索することはありません。
(fad:list-directory "./")
; => (#P"/Users/satoshi/lisp/asdf.fas" #P"/Users/satoshi/lisp/cv_test.txt"
;     #P"/Users/satoshi/lisp/test.txt" #P"/Users/satoshi/lisp/convert.lisp"
;     #P"/Users/satoshi/lisp/asdf.lib" #P"/Users/satoshi/lisp/asdf.lisp"
;     #P"/Users/satoshi/lisp/convert.dx64fsl" #P"/Users/satoshi/lisp/convert.fas"
;     #P"/Users/satoshi/lisp/convert.lib" #P"/Users/satoshi/lisp/.DS_Store"
;     #P"/Users/satoshi/lisp/old/")

walk-directory関数: ディレクトリ内の再帰的探索

この関数は高機能ですので、サンプルを複数示しながら説明します。

まず、必須の引数は2つで、第1引数がディレクトリ、第2引数が関数です。返り値はありません。
(fad:walk-directory "./a/" #'print)
; 
; #P"/Users/satoshi/lisp/a/d.txt" 
; #P"/Users/satoshi/lisp/a/.DS_Store" 
; #P"/Users/satoshi/lisp/a/b/e.txt" 
; #P"/Users/satoshi/lisp/a/b/.DS_Store" 
; #P"/Users/satoshi/lisp/a/b/c/f.txt" 

標準ではディレクトリに対しては関数を適用しませんが、:directoriesキーワード引数をnil以外に指定するとディレクトリにも適用するようになります。
(fad:walk-directory "./a/" #'print :directories t)
; 
; #P"/Users/satoshi/lisp/a/d.txt" 
; #P"/Users/satoshi/lisp/a/.DS_Store" 
; #P"/Users/satoshi/lisp/a/b/e.txt" 
; #P"/Users/satoshi/lisp/a/b/.DS_Store" 
; #P"/Users/satoshi/lisp/a/b/c/f.txt" 
; #P"/Users/satoshi/lisp/a/b/c/" 
; #P"/Users/satoshi/lisp/a/b/" 
; #P"a/" 

ディレクトリの探索は「深さ優先」と「幅優先」があり、標準では「深さ優先」が採用されます。つまり、標準では:directoriesキーワード引数に:depth-firstを指定した場合と同じ動作になります。
(fad:walk-directory "./a/" #'print :directories :depth-first)
; 
; #P"/Users/satoshi/lisp/a/d.txt" 
; #P"/Users/satoshi/lisp/a/.DS_Store" 
; #P"/Users/satoshi/lisp/a/b/e.txt" 
; #P"/Users/satoshi/lisp/a/b/.DS_Store" 
; #P"/Users/satoshi/lisp/a/b/c/f.txt" 
; #P"/Users/satoshi/lisp/a/b/c/" 
; #P"/Users/satoshi/lisp/a/b/" 
; #P"a/" 

これを「幅優先」に変更する場合は、:breadth-firstを指定します。
(fad:walk-directory "./a/" #'print :directories :breadth-first)
; 
; #P"a/" 
; #P"/Users/satoshi/lisp/a/d.txt" 
; #P"/Users/satoshi/lisp/a/.DS_Store" 
; #P"/Users/satoshi/lisp/a/b/" 
; #P"/Users/satoshi/lisp/a/b/e.txt" 
; #P"/Users/satoshi/lisp/a/b/.DS_Store" 
; #P"/Users/satoshi/lisp/a/b/c/" 
; #P"/Users/satoshi/lisp/a/b/c/f.txt" 

さらに、関数を適用するかどうかを判定するための述語関数を個別に指定することもできます。その指定は:testキーワード引数を使います。例えば、拡張子を使って判定を行う場合、ANSI Common Lisp標準のpathname-type関数を使うことができますから、あらかじめ以下のような関数を定めておくことで簡単にファイルの抽出を行うことができます。
(defun filetype-fn (type)
  #'(lambda (pathspec)
      (string= (pathname-type pathspec) type)))
; => FILETYPE-FN

(fad:walk-directory "./a/" #'print :test (filetype-fn "txt"))
; 
; #P"/Users/satoshi/lisp/a/d.txt" 
; #P"/Users/satoshi/lisp/a/b/e.txt" 
; #P"/Users/satoshi/lisp/a/b/c/f.txt" 

一時ファイルに関するオペレータ

CL-FADには一時ファイルを扱うオペレータも複数定義されていますが、その中でも最も便利なwith-output-to-temporary-fileマクロについて簡単に説明します。

プログラムがファイルシステムの中に新しいファイルを生成する際、ファイルの重複には気をつけなければなりません。誤って既存のファイルを上書きしてしまうようなことはあってはなりません。そのため、自身のプログラム名を冠したオリジナルのファイル名を使って、重複がなさそうなディレクトリに保存するのが一般的です。

しかし、ファイルを複数保存する必要がある場合など、名前の重複を丁寧に避けるのは意外と大変な作業です。そこで、重複なくファイルを保存できるようにファイル名を自動で考えてくれるのが「一時ファイル」を扱うオペレータの最も重要な役割です。

CL-FADを用いて一時ファイルを作成するのはとても簡単で2ステップで終わります。

まず、fad:*default-template*スペシャル変数を自分のプログラムの名前などに応じて変更します。
(setf fad:*default-template* "my-program-%")
; => "my-program-%"

最後のパーセント文字は乱数に基づくファイル名が入るので消してはいけません。それ以外の部分を適宜設定します。

そして、with-output-to-temporary-fileマクロで一時ファイルの操作をすると、返り値として一時ファイルのパスネームが返されます。
(fad:with-output-to-temporary-file (temp) (print "Hello" temp))
; => #P"my-program-IADWU5S2"

これだけで一時ファイルを手軽に使うことができるので、必要があればぜひ使ってみてください。

0 件のコメント :

コメントを投稿