FileSystemObjectの使い方まとめ

FileSystemObject。よく使うんだけど、メソッド名とかがいつも思い出せなくなるので、ここにまとめて書いておく。

FileSystemObjectって何?

Windowsファイルシステムを簡単に扱えるようにするためのコンポーネント。最近のWindowsなら最初から標準で入ってる。

VB6やVBA、あとJScript/VBScript(WSHASP)でよく使う。JScript/VBScriptファイルシステムを扱いたい場合、通常はこれを使うしかないと思う。VB6は標準でファイルシステムを扱う関数とかがあるけど、使いにくいので(Openとか使いにくいよね?)FileSystemObjectを使った方がいい。VC++は知らない。.NETには標準で便利なクラスがあるのでFileSystemObjectは使わない。

インスタンス

まずはインスタンス化しないと使えるようにならない。

VB6/VBA

あらかじめ参照設定に「Microsoft Scripting Runtime」を追加しておく。参照設定は、VB6なら「プロジェクト」メニュー、VBAなら「ツール」メニューにある。

Dim fso As New Scripting.FileSystemObject

VBScriptみたいにCreateObjectでもいいけど、せっかくの型情報が使えなくなるので、普通は参照設定をして上記のようにNewする。そして型情報が使えればインテリセンスでメソッド名などは入力補完されるので、いちいち覚えたり調べたりしなくていい。

VBScript
Dim fso : Set fso = CreateObject("Scripting.FileSystemObject")

上記の例ではVBScriptのCreateObject関数を使ってるけど、

  • WSHの場合は WScript.CreateObject メソッド
  • ASPの場合は Server.CreateObject メソッド

も使える。使い方は同じで、引数に"Scripting.FileSystemObject"を渡すだけ。

これらの違いは、2005-02-07が参考になった。

JScript

JScriptっていう言い方がどうも慣れないから以後はJavaScriptって言っちゃうけど、JavaScriptでは以下。

var fso = new ActiveXObject('Scripting.FileSystemObject');

これもVBScriptと同じく、

  • WSHの場合は WScript.CreateObject メソッド
  • ASPの場合は Server.CreateObject メソッド

も使える。

よく使うやつ

個人的によく使うのを以下にまとめる。JavaScriptで書くよ。

パスの結合
var fso = new ActiveXObject('Scripting.FileSystemObject');
var path = 'C:\\data';
var file = 'hoge.txt';
var fullpath = fso.BuildPath(path, file); // "C:\\data\\hoge.txt" が返る

ディレクトリ名とファイル名を結合するときはBuildPathメソッドを使う。ディレクトリ名とサブディレクトリ名を結合しても良い。

単純に文字列結合でやろうとすると、ディレクトリ名の末尾の"\"の扱いとかが面倒になる。このメソッドを使えばそこも自動的にうまくやってくれる。

パスの分解
var fso = new ActiveXObject('Scripting.FileSystemObject');
var fullpath = 'C:\\data\\hoge.txt';
var directory = fso.GetParentFolderName(fullpath); // "C:\\data" が返る
var filename = fso.GetFileName(fullpath);          // "hoge.txt" が返る
var basename = fso.GetBaseName(fullpath);          // "hoge" が返る
var filetype = fso.GetExtensionName(fullpath);     // "txt" が返る

フルパスを元に、

  • そのファイルが存在するディレクトリパス
  • 拡張子を含むファイル名
  • 拡張子を除くファイル名
  • 拡張子のみ

を取り出す。

ファイルのコピー
var fso = new ActiveXObject('Scripting.FileSystemObject');
fso.CopyFile('C:\\data\\hoge.txt', 'C:\\data\\fuga.txt', true);
  • 第1引数はコピー元。ワイルドカードも使える。"C:\\data\\*.txt"みたいに。
  • 第2引数はコピー先。ディレクトリ名も使える(末尾を"\"にする)。
  • 第3引数は上書き許可フラグ。falseにすると、コピー先に同名ファイルが存在していた場合エラーになる。
  • コピー先ディレクトリが存在しない場合エラーになる。
ファイルの移動
var fso = new ActiveXObject('Scripting.FileSystemObject');
fso.MoveFile('C:\\data\\hoge.txt', 'C:\\data\\fuga.txt');
  • 第1引数は移動元。ワイルドカードも使える。
  • 第2引数は移動先。ディレクトリ名も使える(末尾を"\"にする)。
  • 移動先に同名ファイルが存在していた場合エラーになる。
  • 移動先ディレクトリが存在しない場合エラーになる。
ファイルの削除
var fso = new ActiveXObject('Scripting.FileSystemObject');
fso.DeleteFile('C:\\data\\hoge.txt', true);
  • 第1引数は削除するファイル。ワイルドカードも使える。
  • 第2引数をfalseにすると、読み取り専用ファイルを削除しようとした場合エラーになる。
  • 存在しないファイルを削除しようとした場合エラーになる。
ディレクトリのコピー
var fso = new ActiveXObject('Scripting.FileSystemObject');
fso.CopyFolder('C:\\data', 'C:\\data2\\', true);

考え方はCopyFileと同じ。

ディレクトリの移動
var fso = new ActiveXObject('Scripting.FileSystemObject');
fso.MoveFolder('C:\\data', 'C:\\data2\\');

考え方はMoveFileと同じ。

ディレクトリの削除
var fso = new ActiveXObject('Scripting.FileSystemObject');
fso.DeleteFolder('C:\\data', true);

考え方はDeleteFileと同じ。

ディレクトリ内にファイルやサブディレクトリが存在したら、それも全部削除される。

ディレクトリの作成
var fso = new ActiveXObject('Scripting.FileSystemObject');
fso.CreateFolder('C:\\data\\files');

最後のパス以外は既に存在していないとダメ。つまり2階層以上を一気に作ることはできない。ケチ。

ファイルの作成
var fso = new ActiveXObject('Scripting.FileSystemObject');
var stream = fso.CreateTextFile('C:\\data\\hoge.txt', true, false);
stream.WriteLine('hello');
stream.Write('good-bye');
stream.WriteBlankLine(5);
stream.Close();

CreateTextFileメソッドは、空のテキストファイルを作成してTextStreamのインスタンスを返す。以降、ファイルへの書き込みはこのTextStreamのインスタンスを使う。終わったらTextStreamをCloseする。

  • CreateTextFileの第1引数は作るファイル名。
  • CreateTextFileの第2引数は上書き許可フラグ。
  • CreateTextFileの第3引数をtrueにするとUTF-16でファイルを作る。falseにするとShift_JISUTF-8で作ることはできない。UTF-8で作りたいときはFileSystemObjectではなくADODB.Streamを使う。これについてはあとで書く。
  • TextStreamのWriteLineは文字列を書き込んで、最後に改行する。
  • TextStreamのWriteは文字列を書き込む(最後に改行しない)。
  • TextStreamのWriteBlankLineは改行を指定された数だけ書き込む。

できればtry〜finallyで確実にClose()が呼ばれるようにしたいところ。

var fso = new ActiveXObject('Scripting.FileSystemObject');
var stream = fso.CreateTextFile('C:\\data\\hoge.txt', true, false);
try{
    stream.WriteLine('hello');
    stream.Write('good-bye');
    stream.WriteBlankLine(5);
}finally{
    stream.Close();
}
ファイルの読み書き
var fso = new ActiveXObject('Scripting.FileSystemObject');
var stream = fso.OpenTextFile('C:\\data\\hoge.txt', 1, false, -2);

OpenTextFileでファイルを開き、TextStreamのインスタンスを返す。引数の渡し方で読み込み専用だったり書き込みしたりを選ぶ。

わかると思うけど、新規にファイルを作るとき(または既存のファイルを上書きして新しく作るとき)はCreateTextFile。既存のファイルを読み込んだり編集したり追記したりするとき(またはファイルが無かったら新規に作る場合)はOpenTextFile。

  • OpenTextFileの第1引数は読み込むファイル名。
  • OpenTextFileの第2引数はモード。
    • 1 : 読み込み専用
    • 2 : 書き込みもできる
    • 8 : 追記
  • OpenTextFileの第3引数をtrueにすると、ファイル存在しない場合に作成する。
  • OpenTextFileの第4引数は文字コード

終わったらTextStreamをCloseするのを忘れずに。

使用例。読み込むだけなら以下。

var fso = new ActiveXObject('Scripting.FileSystemObject');
var stream = fso.OpenTextFile('C:\\data\\hoge.txt', 1, false, -2);
var text = stream.ReadAll();
stream.Close();

TextStreamのReadAllメソッドで、全部まとめて文字列に読み込む。一行ずつ読み込んだりするメソッドもあるけど、面倒だからあんまり使わない。一行ずつ処理したいときは、ReadAllで最初に全部読み込んでから、改行文字でsplitして配列にして、それを処理する。以下のような感じ。

var lines = text.split(/\r\n/);
for(var i = 0; i < lines.length; i++){
    var line = lines[i];
    // 何らかの処理
}

また、書き込みするときは以下のようになる。

var fso = new ActiveXObject('Scripting.FileSystemObject');
var stream = fso.OpenTextFile('C:\\data\\hoge.txt', 8, true, -2);
stream.WriteLine('hello');
stream.Close();

これは追記の例。

第2引数を2(書き込みモード)にしてファイルを開き、カーソルを移動しながら編集することもできるけど、面倒だからあんまり使わない。編集したいときは、いったんReadAllメソッドで全部読み込んで、その文字列を編集して、CreateTextFileで一気に上書きで書き込んでしまうことの方が多い。

あんまり、ファイルを開きっぱなしにしてちょっとずつ読んだり書いたりするのは好きじゃない。全部読み込んですぐクローズして、メモリ上で文字列編集して、そして新規ファイルとして全部書き込んでしまう方が好き。よっぽど巨大なファイルじゃない限りは、それで十分だと思ってる。開きっぱなしは途中で落ちたりするのが怖い。DB接続の考え方と同じ。外部リソースを掴むのは、コードの範囲も時間も最小限にしたい。

「よっぽど巨大なファイル」というと、ログなんかがそうだと思うけど、ログは追記で良いから気にしない。ちなみに追記の場合も、開きっぱなしにして必要なときに追記していくのではなく、追記する用事があるたびにいちいち開いて、書き込んだらすぐクローズする。開きっぱなしは好きじゃない。

ファイル、ディレクトリの存在確認
var fso = new ActiveXObject('Scripting.FileSystemObject');
if(!fso.FileExists('C:\\data\\hoge.txt')){
    WScript.echo('ファイルが存在しないよ!');
}
if(!fso.FolderExists('C:\\data')){
    WScript.echo('ディレクトリが存在しないよ!');
}

引数で渡したファイルやディレクトリが存在するかを調べてtrueかfalseで返す。

ファイル一覧を取得

GetFolderメソッドでFolderのインスタンスを受け取り、Filesプロパティを使ってファイル一覧を取得する。あとはループで処理。

var fso = new ActiveXObject('Scripting.FileSystemObject');
var dir = fso.GetFolder('C:\\data');
var message = '';
var files = new Enumerator(dir.Files);
for(; !files.atEnd(); files.moveNext()){
    message += files.item().Name + '\n';
}
WScript.Echo(message);

ループがややこしい。Enumeratorクラスを使う。

ちなみにVBScriptではFor Eachが使えるので以下のような感じ。

Dim fso : Set fso = CreateObject("Scripting.FileSystemObject")
Dim dir : Set dir = fso.GetFolder("C:\data")
Dim message : message = ""
Dim files : Set files = dir.Files
For Each file In files
    message = message & file.Name & vbNewLine
Next
WScript.Echo(message)

For Eachが使える部分に関しては便利だけど、それ以外の部分がいちいち冗長な気がする。そこがVBクオリティ。

このループ中に取得できるのが、Fileのインスタンス。上記の例ではNameプロパティでファイル名を取得している。この他にも、

  • Sizeプロパティでファイルサイズを取得
  • Copyメソッドでコピー
  • Moveメソッドで移動(リネーム)
  • Deleteメソッドで削除
  • OpenAsTextStreamメソッドでファイルオープンしてTextStreamのインスタンスを返す

などができる。

ディレクトリ一覧を取得

上記のFilesの代わりにSubFoldersを使うだけ。

var fso = new ActiveXObject('Scripting.FileSystemObject');
var dir = fso.GetFolder('C:\\data');
var message = '';
var folders = new Enumerator(dir.SubFolders);
for(; !folders.atEnd(); folders.moveNext()){
    message += folders.item().Name + '\n';
}
WScript.Echo(message);

VBScriptなら当然For Eachを使って書ける。

ループ中に取得しているのが、これもまたFolderのインスタンス。上記の例ではNameプロパティでディレクトリ名を取得している。Fileのインスタンスと同様に、

  • Sizeプロパティでディレクトリ内のファイルサイズの合計を取得
  • Copyメソッドでコピー
  • Moveメソッドで移動(リネーム)
  • Deleteメソッドで削除
  • CreateTextFileメソッドでディレクトリ内に新規ファイル作成

などができる。また、最初にfso.GetFolder(パス)で取得しているのと同じクラスなので、FilesプロパティやSubFoldersプロパティで、さらに中のファイルやディレクトリも処理できる。普通は再帰を使うと思う。

終わり

もっとたくさん機能はあるけど、自分がよく使うのは以上。もっと詳しく知りたい場合はMSDN

を参照。

解放は必要?

ところで、FileSystemObjectって、使ったあとの解放は必要?

var fso = new ActiveXObject('Scripting.FileSystemObject');
//
// fso を使った処理
//
fso = null;
Dim fso : Set fso = CreateObject("Scripting.FileSystemObject")
'
' fso を使った処理
'
Set fso = Nothing

こんなやつ。最後のnullやNothingを代入するような部分。

よくこういう風に書いてるコードがあるけど、fso自体はファイルシステムへのアクセス手段を提供しているだけで、何か外部リソースを掴んでるわけではないから、解放処理は不要だと思っている。MSDNに載ってるのサンプルコードでもやってないし。

でも自信ない。教えて欲しい。