第19章「ファイルネーム」

概要

ANSI Common Lispの第19章「ファイルネーム」を説明します。

ファイルネームの概要

ANSI Common Lispの仕様は最後の用語集の章を除けば25の章で構成されています。その中でも「ファイルネーム」という項目が独立した章になっている点は非常に特徴的です。

C言語やPerlなどはもともとUnixのために設計されており、そこでのファイル名は暗黙的にUnixにおける Path を指しています。また、PythonはUnixではないOSのための言語でしたが、現在ではGNU/Linuxシステム上で使用することが多く、多くのOSで共通してファイル名を適切に扱うことはかなり難しいのです。

Lispは長い歴史があり、実に様々なOSで使われてきました。そのため、ファイル名を単純に文字列として扱おうとすると、あるOSではパスを指すのに使われるのに、別のOSではパスとしての意味をなさない、ということが起こり得ます。そこで、ANSI Common Lispの仕様ではファイル名を文字列ではない独立したオブジェクトとして規定しています。

ただ、ANSI Common Lispの仕様の中には止むを得ず処理系依存になっている部分も多く、ファイル名の可搬性(様々なOS・処理系で使用できること)を高めたければ、サードパーティ製のライブラリをする場合が多いです。おそらく、最もたくさん利用されているライブラリはCL-FADであり、近年ではASDFに含まれるUIOPが使われることもあるかもしれません。UIOPはCL-FADを含む様々な処理系依存処理を置き換えるライブラリを目指して設計されています。ASDFの補助ライブラリのため、処理系に含まれることが多いASDFをロードすればUIOPを使うことができます。

このページでは、macOS/UNIXを想定して、ANSI Common Lispの範囲内でファイルネームの基礎を紹介します。

パスネームオブジェクト

OSにおけるファイルネームは、Common Lispでは「パスネーム」という抽象化されたオブジェクトになります。

パスネームオブジェクトを生成する最も簡単な方法は、#pリーダーマクロを使うことです。
#p"test.txt"
; => #P"test.txt"

#p"testdir/test.txt"
; => #P"testdir/test.txt"

#p"../testdir/test.txt"
; => #P"../testdir/test.txt"
#pリーダーマクロはpathname関数に展開されます。
(pathname "test.txt")
; => #P"test.txt"

もう少し複雑にパスネームを構築したい場合、make-pathname関数を使うことができます。
(make-pathname :directory "testdir" :name "test.txt")
; => #P"/testdir/test.txt"

make-pathname関数は8つのキーワード引数を使うことができます。最も頻繁に利用するのは、上の例で示した:directory:nameです。これらはそれぞれ「ディレクトリ」(または「フォルダ」)と「ファイル名」に該当します。

ディレクトリの指定をどのように行うかは処理系依存ですが、多くの処理系では:relative:aboluteを使うことができます。処理系のマニュアルに必ず書いてあるはずなので、各自で確認してください。
(make-pathname :directory '(:relative "testdir") :name "test.txt")
; => #P"testdir/test.txt"
(make-pathname :directory '(:relative "testdir") :name "test.txt")
; => #P"testdir/test.txt"

処理系によってはより柔軟な指定が可能な場合もあります。GNU CLISPでは現在のディレクトリの一つ上のディレクトリを指す".."や、それと等価な:upなどを使うこともできます。
(make-pathname :directory '(:relative "..") :name "test.txt")
; => #P"../test.txt"
(make-pathname :directory '(:relative :up) :name "test.txt")
; => #P"../test.txt"

もっと別の階層を指定する場合は、:directoryキーワード引数でのリストの指定に含めます。
(make-pathname :directory '(:relative :up :up) :name "test.txt")
; => #P"../../test.txt"
(make-pathname :directory '(:relative "d1" "d2") :name "test.txt")
; => #P"d1/d2/test.txt"

Windows PCの場合は:deviceキーワードを使うことがあるでしょう。UNIX/Linuxでは「マウント」という技術があるので様々なデバイスを統一的な Path で扱うことができますが、Windowsの場合は"C:\Documents\test.txt"のように最初にデバイスを付けます。このデバイスはAからZまでの1文字のアルファベット文字が割り当てられますので、:deviceで指定します。

その他の引数には:host, :version, :defaults, :caseなどがありますが、必要に迫られる場合はあまりないでしょう。必要な場合は自分のOSと処理系の組み合わせに合わせてマニュアルを確認してください。唯一、パスネームを構築するときにはあまり使わないかもしれませんが、:typeだけは知っておいてもいいかもしれません。これはファイルの種類を示しますが、どのように扱われるかは処理系依存です。一般的には「拡張子」と呼ばれる部分が文字列で格納されます。
(make-pathname :directory '(:relative :up :up) 
               :name "test" 
               :type "txt")
; => #P"../../test.txt"

もっともUNIXでは拡張子にあまり意味はないので、:nameでまとめて指定した方がいいかもしれません。

パスネームオブジェクトはこれらの要素が個別にスロットに格納されているため、以下のような関数でアクセスすることができます。
(pathname-directory "../testdir/test.txt")
; => (:RELATIVE :UP "testdir")
(pathname-name "../testdir/test.txt")
; => "test"
(pathname-type "../testdir/test.txt")
; => "txt"

文字列への変換

前節で見たように文字列からパスネームオブジェクトを生成するにはpathname関数または#pリーダーマクロを使用します。

逆にパスネームオブジェクトから文字列に変換するにはnamestringを使用します。
(namestring "../testdir/test.txt")
; => "../testdir/test.txt"

パスネーム文字列の一部を取得したい場合はfile-namestring関数やdirectory-namestring関数を使用します。
(file-namestring "../testdir/test.txt")
; => "test.txt"
(directory-namestring "../testdir/test.txt")
; => "../testdir/"

絶対パスと相対パスの連結

場合によってはパスの連結を行いたい場合もあります。特に、絶対パスと相対パスを連結するような場合です。

そのような時はmerge-pathnames関数を使用します。
(merge-pathnames "aa/bb/cc.txt" "/usr/local/")
; => #P"/usr/local/aa/bb/cc.txt"

しかし、この関数は引数が2つまでしか使えず、また、絶対パスが後ろで相対パスが手前という扱いになっているので、あまり使われないように思います。このような操作が必要な場合は、CL-FADのmerge-pathnames-as-file関数やmerge-pathnames-as-directory関数を使用する場合が多いかもしれません。ASDFが使える状態でCL-FADをロードし、以下のように使用してみてください。
;; 3つ以上でも指定できる
;; 絶対パス側から指定できる
(fad:merge-pathnames-as-file "/usr/local/" "aa/bb/" "cc.txt")
; => #P"/usr/local/aa/bb/cc.txt"

;; merge-pathnames-as-directory は常にディレクトリ部分を返す
(fad:merge-pathnames-as-directory "/usr/local/" "aa/bb/" "cc.txt")
; => #P"/usr/local/aa/bb/"

(fad:merge-pathnames-as-directory "/usr/local/" "aa/bb/")
; => #P"/usr/local/aa/bb/"

;; 最後の / を忘れると、1つ上のディレクトリになるので注意
(fad:merge-pathnames-as-directory "/usr/local/" "aa/bb")
; => #P"/usr/local/aa/"

0 件のコメント :

コメントを投稿