2013 05 25

zip圧縮

高幡不動〜百草園〜聖蹟桜ヶ丘と散歩。 久しぶりに百草丘陵を通ったのだが、こっちは川沿いと違ってあまり変化が無いな。 団地が地味に劣化してるぐらい。

傷跡の1

何がどうなってこんな痕が出来るんだろう。 大きな枝が、台風か何かで折れて落ちたとか?

傷跡の2

似たような痕をもう一つ。 小さな瘤が出来ているのは、瑕が塞がったところで維管束が塞がったために、葉で作られた養分が溜まるからだろうか。

+

WindowsXP以降、OSに標準でZIP圧縮/解凍の機能がついたのはいいのだが、explorerを介してしかこの機能を使うことが出来ないのが地味に不便だったりする。 コマンドラインから操作出来ると、例えば 「特定のフォルダの特定の拡張子のファイルを同じ名前のZIPファイルに圧縮する」 なんてことが簡単に出来て便利なのに。 でも、コマンドとして提供されていなくても、APIとしては存在するはずだから、VBScriptかJScriptでちょっと頑張れば出来そうだな。

そう思いながら長いこと放置していたのを、ふと思い出して、実際に出来るかどうかを調べてみた。

結論から言うと、簡単に出来た。 それ用のAPIを呼び出さなくても、explorerの機能、厳密にはshellオブジェクトの機能を使えばいいんだね。 手順は次の通り。

  1. 空のZIPファイルを作る。
  2. これをフォルダに見立ててファイルをコピーする。

エラーチェックなんかを全てすっとばして、エッセンスだけ抜き出して VBScript で書くとこんな感じ。

Option Explicit Dim fs: Set fs = CreateObject( "Scripting.FileSystemObject" ) Dim sh: Set sh = CreateObject( "Shell.Application" ) Dim zipName: zipName = fs.GetAbsolutePathName( WScript.Arguments( 0 ) ) Dim srcPath: srcPath = fs.GetAbsolutePathName( WScript.Arguments( 1 ) ) With fs.CreateTextFile( zipName, True ) .Write "PK" & Chr( 5 ) & Chr( 6 ) & String( 18, 0 ) .Close End With With sh.NameSpace( zipName ) .CopyHere( srcPath ) Do Until .Items().Count = 1 Wscript.sleep 100 Loop End With

コマンドプロンプトから実行するZIP圧縮なので、仮に czip.vbs としておく。

処理のポイントは、非同期で実行されるらしいコピーの完了を明示的に待たないといけないこと。 待たないと、空のzipファイルが出来ただけで終わってしまう。

完了の判定は、zipファイル内のオブジェクト数がコピーした数と同じになったかどうかで行う。 上の例では、第2引数で指定したファイルを一つだけコピーしているので、判定対象のファイル数も1を直接書いている。

個人的には VBScript よりも JScript の方が好きなので、できれば JScript で書きたい。 でも JScript だとバイナリデータが上手く扱えない。 なので、初段の空のZIPの作成が出来ない。 どうしよう。 そこだけ ScriptControl を使って VBScript のコードを埋め込むか? なんてことも考えたのだが、あれこれ悩んだ末にググったらあっさり解決した。 String.fromCharCode でいけるんだね。

ということで、JScript版の czip.js

var fs = new ActiveXObject( "Scripting.FileSystemObject" ); var sh = new ActiveXObject( "Shell.Application" ); var zipName = fs.GetAbsolutePathName( WScript.Arguments( 0 ) ); var srcPath = fs.GetAbsolutePathName( WScript.Arguments( 1 ) ); var zipFile = fs.CreateTextFile( zipName, true ); zipFile.Write( "PK" + String.fromCharCode( 5,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ) ); zipFile.Close(); var zipFolder = sh.NameSpace( zipName ); zipFolder.CopyHere( srcPath ); while ( zipFolder.Items().Count != 1 ) { WScript.sleep( 100 ); }

エッセンスだけ抜き出したと言ったが、実はこれだけでもちゃんと動く。 例えば、test.log を圧縮して test.zip を作る場合は、コマンドプロンプトから以下を実行する。

cscript czip.vbs test.zip test.log

末尾引数で指定する圧縮対象はファイルでもフォルダでもいけるが、ワイルドカードは使えない。

CopyHere はワイルドカードにも対応しているらしいのだが、ここでワイルドカードを使うと、コピーの完了判定が問題になるんだよね。 上のコードで1限定にしているのを、ワイルドカードを展開したときに該当するファイル数をあらかじめ取得して、これを比較するようにしなければいけない。 って、書いてみると簡単そうだな。 そのうちやってみるか。