ファイルと移動の日々

スタックも含めて、コンピュータのデータは全てファイルとして保存される。だから電子的なデータ処理のためには、ファイルを操作する命令が欠かせない。ところが残念なことに、ハイパーカードはファイルの扱いが不得手である。この弱点を補うにはXCMDを使うしかない。

HyperTalkとファイル操作

HyperTalkで用意されている基本的なファイル操作の命令open, read, write, closeでも、最低限のテキストファイルの読み書きは可能だ。確かにこれだけでも、簡単なテキストデータベースは作成できる。とりあえずテキストを読むことさえできれば、あとは内部の文字列操作で情報を扱えるからだ。

しかしあるレベル以上の処理を行うには、ファイルの更新時刻をチェックしたり、コピーや移動、削除などの手段が必要になる。多くのファイルを対象にするなら、フォルダ内のファイルの一覧を取得することも不可欠だ。HyperTalkでもリスト1のようにすればテキストファイルの内容をコピーすることはできないわけではない。けれども、この方法では作成日付やクリエイターなどの情報は失われてしまうし、ましてやテキスト以外のファイルのコピーは不可能である。ファイル一覧にいたっては、まったく考慮の外だ。

本来これくらいの命令は標準で備えておいて欲しいものだが、ないものねだりをしても仕方がない。こういうときこそ、XCMDの出番である。幸いなことに、リナルディ・コレクションには多くのファイル操作命令が用意されている。今回はこれらのXCMDを取り上げ、それによって実現できる機能について検討してみよう。

ファイルについて知る

知らない町に行くには、そこの地図を見ることからスタートする。ファイル操作ならばファイルについての情報を知らなければならない。まずはディレクトリ(フォルダ)内のファイルの一覧の取得からだ。リストを得る基本型は

put GetDir(pathname) into fileList

のようになる(画面1)。この命令は、オプションを使って豊富な条件でファイルを選択できる。

GetDir(<pathname>[,<mode>][,<filter 1>...[, <filter n> ]])

pathName

ファイルリストを取得するフォルダのパス

mode

リストアップする対象をファイルにするか、フォルダにするか。FならpathNameにあるファイルを、Dならフォルダを、Aなら両方のリストを返す。後ろに+を加えると、サブフォルダの中まで含めてリストアップする(省略値はA)。

filter

リストアップするファイル/フォルダを様々な条件で絞り込む。複数のfilterを設定すると、それらはAND条件(全てを満たすもの)として扱われる。指定の方法は "X◇Value" のような形で、Xは下に示すフィルタのタイプ、◇はタイプと値の関係を示す論理演算子だ。

フィルタのタイプとしては次のものがある:

c,t : ファイルのクリエータ、タイプ
e,m,b : 作成、更新、バックアップの日付
x,y,z : 作成、更新、バックアップの時刻
l,s : ファイルの論理サイズ、物理サイズ
d,r : データフォーク、リソースフォークのサイズ
f : フォルダ内のファイルの数
n : ファイル/フォルダ名
i : 不可視ファイルを対象にする(TRUE/FALSE)
o=MASK : リストアップした結果をマスクによって並べ替える。MASKは c,d,e,f,m,n,p,r,s,t,v,x,y のいずれかで、フィルタのタイプと同じ意味を持つ(pはパス名、vはヴァージョン)。 論理演算子としては
=, ュ, <, >, ウ, イ
の6とおりが使える。nフィルタの場合はそれぞれValue(名前)に対して:
一致する、一致しない、で始まる、で終わる、を含む、含まない
という意味になる。

例えば今日の午後5時以降に作成したテキストファイルをリストアップするなら

GetDir(pathname, "F", "m=" & the date, "y>5:00 PM", "t=TEXT")

となる。また、nフィルタを使って、MS-DOS風のファイルの拡張子を基準に選ぶという方法がある。

GetDir(pathname, "F+","n>.html")

とすれば、ホームページに使うHTMLファイルだけを調べることが可能だ(画面2)。ホームページの規模が大きくなってくると、いったいどれだけの情報があるのかを把握するのが自分でも困難になるが、この1行だけですべてのフォルダにあるHTMLファイルをリストアップすることができる。ホームページ管理用にはありがたい。

個々のファイルに関する詳しい情報が必用な場合はGetFInfoを使う。

put GetFInfo(filename) into MyInfo

とすると、作成・更新日やクリエータ、タイプなどの25アイテムの情報が取得できる(画面3)。この情報を調べることで、ファイルごとにきめ細かな処理を行うことが可能だ。

コピーする

ファイルをコピーするにはFileCopyというXCMDを使う。この極めて基本的な機能がHyperCardには用意されていないから、頻繁にお世話になるツールだ。普通にファイルをコピーするには

FileCopy FileA, FileB

とする。FileBはコピー先のフォルダのパス(*注1)でも良い。これまで見てきたものが全て関数形式の外部命令(XFCN)であったのに対し、これはコマンド形式のXCMDである。オプションは次のようなものがある。

FileCopy <srcfile> [,<dstfile>] [,<replace>] [,"DontResolveAlias"] [,"DontShowProgress"]

SrcFile

コピーする元になるファイル。フォルダのパスなしでファイル名のみを与えると、スタックと同じフォルダにあるものとみなす。

DstFile

コピー先のファイル/フォルダ名。ファイル名だけの場合は元と同じフォルダに指定された名前でファイルを作る。フォルダ名だけなら、そのフォルダに元と同じ名前のファイルを作る。まったく省略すると、「複製」と同じくCopy of ...というファイルを作る。

replace

コピー先に同じ名前のファイルが存在したときに上書きするかどうかをTRUE/FALSEで指定。省略値はFALSEで、上書きしない。

"DontResolveAlias"

この文字列そのものを引数として渡すと、エイリアスをオリジナルに展開せず、エイリアスとしてコピーする。省略すればオリジナルファイルを探してコピー。

"DontShowProgress"

FileCopyは、ファインダと同様コピーするときに作業の経過を示すプログレスバーを表示するが、この文字列を引数として渡すと、これを表示しない。

replaceオプションはファイルのアップデートの時に便利である。これがないと、いちいち古いファイルを消去する命令を実行してからコピーしなければならない。

まとめてコピーするならCopyFolderを使う。

CopyFolder FolderA, DiskB

とすると、ファイルをフォルダごと別のディスクにコピーできる。もちろんサブフォルダもまとめてコピーしてくれる。

CopyFolder <srcfldr>[,<dstpath>] [,<files>] [,"ResolveAlias"] [,"DontShowProgress"] [,"KeepPrivileges"]

SrcFldr

コピー元のフォルダ

DstPath

コピー先のディスクやフォルダのパス。省略したりフルパスでなく名前のみの時はFileCopyと同様、元のフォルダを含むフォルダ内にCopy of...や指定した名前のフォルダを作る。

Files

これをFALSEにすると、ファイルをコピーせずフォルダだけ階層通りにコピーする。省略値はTRUE、つまりファイルも一緒にコピー。

"ResolveAlias"

この文字列をわたすと、エイリアスをオリジナルに展開する。省略値するとエイリアスのままコピー。FileCopyと逆の関係になることに注意。

"DontShowProgress"

FileCopyと同様、経過を表示しない。

"KeepPrivileges"

共有ボリュームでのアクセス権も保持してコピーする。省略すると、保持しない。

この命令は、FileCopyと違って上書きはしない。コピー先に同じフォルダがあるとエラーになる。

移動、消去、名前変更

ファイル操作をXCMDで行おうというのは、ファインダで手作業で行っていることをすべてHyperTalkによって自動的に処理させたいからだ。コピーだけでなく、移動、消去、名前変更のためのXCMDももちろん用意されている。

移動の場合は

FullMove Filename, DestPath

となる。Filenameがフォルダ名の場合はフォルダをそっくり移動する。DestPathは移動先のパス(移動元のファイル自身の名前を含んではならない)。

消去するときには

FullRemove Filename

を使う。これもフォルダ名を与えると、フォルダごと全て消去する。この命令はゴミ箱に捨てるのではなく、即座に消去してしまう(取り消し不可)ので注意が必要だ。

さらに名前を変更しようと思ったら

FullRename OldPathName, NewName

がある。OldPathNameは元のファイル/フォルダのパス名(名前だけの場合はスタックと同じフォルダにあるとみなす)。NewNameは、パスを含めず新しい名前のみを指定する。

これら全ての命令に、FileCopyと同じく"DontResolveAlias"オプションが用意されている。省略した場合は、エイリアスをオリジナルに展開してから処理する。

ブリーフケースの作成

HyperBriefcase Windows95ではブリーフケースという、複数のコンピュータのファイルを同一に保つような手段が用意されているが、今回のXCMDを使って同じような機能を実現してみよう。指定された2つのフォルダの内容を比べ、更新されたファイルをコピーすることで、ファイルを常に最新の状態で統一しようというものである(画面4)。

基本的な材料は、GetDir, GetFInfo, FileCopyの3つである。リスト2を見てみよう。このハンドラは、コピー元になるフォルダ(sPath)のファイルをリストアップし、コピー先のフォルダ(dPath)と比較して新しいファイルがあればコピーしていくという、ブリーフケースの中心となる作業を受け持つ。

まず2行目でコピー元のファイルをリストアップする。フィルタオプションとして"o=pmy"を指定しているので、結果として得られるファイルリスト(sList)の各行は

フルパス名,更新日付,更新時刻

という3つのアイテムから構成されていることに注意しよう。3行目は、このリストから1列目(フルパス名)だけを抜きだし、コピー先のフォルダのパスと全置換することで、コピーされる側のファイルのリスト(dList)を作成しているところだ。このsList, dListという2つのリストを使って、4〜22行のループでアップデートの作業を進めていく。

8〜11行は、コピー先にフォルダが存在するかどうかを確認する部分。コピー元のフォルダが新しく作られたものだと、対応するフォルダを最初に用意しなければそれ以降のコピーができないからだ。リナルディ・コレクションにはフォルダ作成用のXCMD(CreateFolder)も用意されていおり、抜かりはない(9行目)。

12行目でコピー先にファイルが存在するか(つまり新規作成ファイルかどうか)をチェックし、新規ファイルなら無条件にコピーする。ファイルが既に存在する場合は、うっかり古いファイルで上書きしないよう確認しなければならない。15〜16行目は、最初のGetDirで得た情報から、コピー元のファイル更新日時をsecond形式に変換する(*注2)。17〜19行目は、コピー先ファイルの更新日時の確認だ。GetFInfoの5、6番目のアイテムがそれぞれ更新日、更新時刻情報になっていることを利用している。この2つの更新日時を比べ、コピー元の方が新しいファイルであれば、20行目で上書きコピーを行う。

実際には、エラーチェックや、対象となるファイルを特定の日に更新されたものに限るなど、さらに細かい作り込みを行う必要があるが、基本はこういう流れだ。オフィスと自宅の両方で作業する場合、フォルダの中身をシンクロさせておく作業が欠かせないが、このようなツールを用意するとコピー漏れなどのミスを防ぐことができる。あるいは簡易バックアップツールとして利用しても役立つことだろう。

何度も繰り返すが、ファイル操作はデータ処理の基本中の基本である。これらのXCMDの使い方を研究し、ファイルの扱いをぜひ手中に収めたいものだ。

*注1

用語として少々紛らわしいが、パスと表記した場合は、ファイルの存在するフォルダへのパス(ファイル名を含まない)、パス名と表記した場合はファイル名も含むフルパスを示す。たとえばFileAがHD:MacUser:というフォルダにあるとすると、パスはHD:MacUser:、パス名はHD:MacUser:FileAということになる。パス名をフルパスと書くこともある。

*注2

リナルディ・コレクションでは、ファイルの更新情報は日付と時刻が別々になったフォーマットでしか得られない。別のXCMDを使えば最初からこれをsecond形式で取得することも可能だが、ここではリナルディ・コレクションだけで作成してみた。

リスト

リスト1

-- copy from text file File_A to File_B

open file File_A
read from file File_A until EOF
close file File_A
open file File_B
write it to file File_B
close file File_B

リスト2

-- sPathはコピー元のフォルダ、dPathはコピー先のフォルダ

 1: on updateCopy sPath, dPath
 2:   put GetDir(sPath, "A+", "o=pmy") into sList
 3:   put FullReplace(ExtractItems(sList,1),sPath,dPath) into dList
 4:   repeat with i=1 to number of lines of sList
 5:     put line i of sList into sInfo
 6:     put item 1 of sInfo into sFile
 7:     put line i of dList into dFile
 8:     if last char of dFile is ":" then
 9:       if there is NOT a folder dFile then CreateFolder dFile
10:       next repeat
11:     end if
12:     if there is NOT a file dFile then
13:       FileCopy sFile, dFile, TRUE
14:     else
15:       put item 2 of sInfo && item 3 of sInfo into sDate
16:       convert sDate to seconds
17:       get GetFInfo(dFile)
18:       put item 5 of it && item 6 of it into dDate
19:       convert dDate to seconds
20:       if sDate > dDate then FileCopy sFile, dFile, TRUE
21:     end if
22:   end repeat
23: end updateCopy

(MacUser Japan, August 1996)