ディレクトリサイズを取得するVBScriptをPHPから起動
たくさんのWebサイトを運営しはじめるとなるべく作業の自動化をしたいと考えはじめるのは自然なことだと思います。
例えば、以前書いた記事で言うと、このあたり。
- WSHでブラウザを自動操作する
- PHPでファイルとDB(PostgreSQL)をバックアップ
- レンタルサーバーでバックアップしたファイルを自動DLする
- Windows版PHPでSFTP接続するにはphp_ssh2.dllが必要
- WinSCPで更新されたファイルのみバックアップする
社内あるいは自宅内に24時間稼働するWindows PCを置いてタスクスケジューラーで上記のスクリプトを定期実行するようにさえすれば、ブラウザ操作からDBバックアップ&DBの自動DLに加え、毎回フルバックアップするのは少々つらい画像類の差分バックアップまで全自動化できてしまいます。
すると今度は、その24時間稼働中のWindows PCのメンテナンスも自動化したくなってきます。
PHPのfilesize関数は2GB以上のファイルを扱えない
外部のWebサーバー上のデータをローカルPCに毎日バックアップしていると、ローカル側のディスク容量も気になってきます。そのため、ローカルPC内の各種フォルダー内の使用量をチェックするPHPスクリプトを作ってみました。
ところが、一部のディレクトリだけどうしてもエラーが出てファイルサイズが取れません。自分のコーディングが悪いのかなと、綺麗なコーディング例も探してみました。例えばこれなんかシンプルでわかりやすいですよね。
■PHPでフォルダーサイズを取得する例
function get_folder_size ($folder_name)
{
$size = 0;
foreach (glob(rtrim($folder_name, '/').'/*', GLOB_NOSORT) as $each) {
$size += is_file($each) ? filesize($each) : get_folder_size($each);
}
return $size;
}
ぼくが作るスクリプトだとscandir関数を使ってゴリゴリ書く感じですが、こちらのスクリプトではワイルドカードを指定できるglob関数を使って、スマートに書いているのが素敵です。
しかし、このスクリプトでも一部のディレクトリだけエラーが出てどうしようもないため、あらためてfilesize関数のマニュアルを読んでみることにしました。
やはりファイルサイズを返す関数で間違いありませんし、使い方もシンプルです。 しかし、返り値の注意書きの部分をよく読むと、filesize (PHP 4, PHP 5, PHP 7) filesize — ファイルのサイズを取得する 説明 int filesize ( string $filename ) 指定したファイルのサイズを取得します。
注意: PHP の数値型は符号付整数であり、 多くのプラットフォームでは 32 ビットの整数を取るため、 ファイルシステム関数の中には 2GB より大きなファイルについては期待とは違う値を返すものがあります。
おぅふ……。
まさか2GB以上のファイルがあると正常にサイズを取得できないとは。
解決策はいろいろあると思いますが、ぼくの場合はVBScriptに慣れているので、ディレクトリサイズを標準出力に表示するスクリプトを組みました。
VBScriptでフォルダサイズを表示する
■サンプル
'********************************************************************
'* フォルダサイズを表示する
'********************************************************************
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objArg = WScript.Arguments
'//1つ目の引数が存在しない場合
If objArg.length <= 0 Then
WScript.Quit
Else
'1つ目の引数をディレクトリ名とする
strPath = objArg(0)
End If
Set objFolder = objFSO.GetFolder(strPath)
WScript.Echo objFolder.Size
一応、後で機能追加するかも知れないと思い、引数チェックもしていますが、ローカル(やイントラネット内)で、自分しか使わないのならこちらの2行で良いと思います。
■手抜きサンプル (folder_size.vbs)
Set objFSO = CreateObject("Scripting.FileSystemObject")
WScript.Echo objFSO.GetFolder(WScript.Arguments(0)).Size
■コマンドプロンプトでの使用例
cscript folder_size.vbs "C:\TargetFolder"
実際に実行してみるとよくわかりますが、PHPでディレクトリを再帰的に検索しながらファイルサイズを計算するよりも、VBScriptのFileSystemObjectのGetFolderを使ったほうが圧倒的に早いです。
PHPでVBScriptの標準出力を受け取る
うちの場合、Windows PCを24時間稼働させていますが、タスクスケジューラーで定期実行しているのはPHPスクリプトがメインです。そのため、おおまかな自動タスクの流れはPHPスクリプトに任せたいので、PHPの内部でVBScriptを起動して、その返り値だけもらっています。
■PHPスクリプトからVBScriptを起動する例
function get_folder_size($folder_name) {
exec ('cscript //Nologo folder_size.vbs "'.$folder_name.'"', $output);
return $output[0];
}
exec関数でVBScriptを起動し、標準出力に書き込まれたフォルダーサイズを$output変数に受け取って返しています。
どうせ1行しか受け取らないのでexec関数はsystem関数に変えても良いですね。
cscriptは起動時にロゴが出てしまうので、//Nologoオプションで表示しないようにしています。
あぁ、そうなると、ますます最後の1行だけ取得するsystem関数のほうが便利かも。
なんでexec関数で作ったんだろう…。
これも後で機能拡張するつもりだったのかも知れない。ソース内にコメントはしっかり書いておかないといけませんね(;´∀`)
まとめ
- PHPのfilesize関数は2GB以下のファイルしかサイズを取得できない
- 2GB以下のファイルしかなかったとしてもPHPでフォルダサイズを取得するのは遅い
- PHPからフォルダーサイズを調べるVBScriptや、なんだったらdirコマンドでも呼び出したほうが楽&早い
とまぁ、こんなところでしょうか。
我が家で24時間稼働しているWindows PCは、運営しているWebサイトのメンテナンスや監視作業の自動化で活躍している上、ファイルサーバーとしても使っているので、各共有フォルダーのサイズを自動取得して、LAN内のWebページに表示したり、ディスク使用量が増えすぎた場合は警告メールを送信するような仕組みを入れています。
やっぱり自動化は燃える。