WSH/JScriptから,コマンドプロンプトを呼び出すライブラリ (WSHとBATを効率よく連携させる案)

 

WSHからコマンドプロンプトを自由自在に呼び出そう,というライブラリの案。

コマンドの実行結果の出力に対してeach()などのイテレータを直で実行できるコーディングスタイルが売り。 ====

// ファイルパス文字列からファイル名を抽出
String.prototype.file_path_to_file_name = function(){
	return this.replace(/^.+\\([^\\]+)$/, "$1");
};


// ファイルパス文字列からフォルダパスを抽出
String.prototype.file_path_to_dir_path = function(){
	return this.replace(/\\[^\\]+$/, "");
};



// 配列のイテレータ
Array.prototype.each = function( func ){
	for( var i = 0; i < this.length; i ++ ){
		func.call( this, this[i], i ); 
	}
	return this; // チェインを継続
};

// map
Array.prototype.map = function( func ){
	var _arr = [];
	this.each(function( item, ind ){
		_arr.push( func.call( _arr, item, ind ) );
	});
	return _arr;
};


function log(s){ WScript.Echo(s); }

//
function err(s){
	try{
		WScript.StdErr.WriteLine(s); 
		// リダイレクトされずにコンソールにも表示される
	}catch(e){
		// GUIで実行された場合は標準出力だけでよい
	}
	
	log(s);
}



// コマンド実行結果を行ごとに配列として取得
// http://d.hatena.ne.jp/language_and_engineering/20081225/1230198688
function exec_cmd( str_cmd, params ){

	// ------ パラメータを取得 -----

	// コマンドを実行したいカレントフォルダのパス
	var current_dir    = ( params ) ? params.current_dir : null;
	
	// カレントフォルダを,このスクリプトの存在するフォルダとするか
	var current_dir_same_as_this_script = ( params ) ? params.current_dir_same_as_this_script : false;
	
	// 通信コマンドが終了するまで待つかどうか
	var wait_while_net = ( params ) ? params.wait_while_net : false;
	
	// 出力行から一行抜き出すための正規表現
	var get_one_output_re = ( params ) ? params.get_one_output_as : null;

	// 出力行から抜き出した一行をキャプチャするための文字列
	var capture_output_str = ( params ) ? params.capture_output_as : null;

	// コマンドを実際には実行しないデバッグモード
	var just_debug_mode = ( params ) ? params.just_debug : false;
	
	// ------ パラメータ取得終わり -----
	

	var ws = WScript.CreateObject("WScript.Shell");
	
	// 必要ならカレントフォルダをセット
	if( current_dir ){
		ws.CurrentDirectory = params.current_dir;
	}else
	if( current_dir_same_as_this_script ){
		ws.CurrentDirectory = WScript.ScriptFullName.file_path_to_dir_path();
	}

	// もしデバッグモードなら,ここで終了
	if( just_debug_mode ){
		err( "カレントフォルダ:" + ws.CurrentDirectory + ", コマンド:" + str_cmd );
		err( "デバッグモードのため,コマンドは実行しません。" );
		
		return;
	}

	err( "実行します:カレントフォルダ:" + ws.CurrentDirectory + ", コマンド:" + str_cmd );

	// コマンド実行
		var proc = ws.Exec( "cmd /c " + str_cmd );
	
	// もしメール送信など通信系のコマンドであれば,終了まで待つ
	if( wait_while_net ){
		while( proc.Status == 0 ){
			WScript.Sleep(100);
		}
		// http://d.hatena.ne.jp/language_and_engineering/20100913/p1
	}
	
	// 出力を取得
	var str_out = proc.StdOut.ReadAll();
	
	// 出力の末尾の空行を削除
	var output_arr = str_out.split("\r\n");
	output_arr.pop();
	
	// 出力から一行だけ取り出す?
	if( get_one_output_re ){
		log("出力から一行取り出します。");
		var match_result = null;
		
		// 行ごとに検査
		output_arr.each(function(line){
			var m = line.match( get_one_output_re );
			
			// 初回マッチについて
			if( m && ( ! match_result ) ){
				// マッチ結果からキャプチャした文字列を保管
				match_result = line.replace(
					get_one_output_re, 
					capture_output_str 
				);
					err("マッチ結果を発見:" + match_result);
			}
		});
		
		// 全行を検査し終わってもマッチしていなかったらNG
		if( ! match_result ){
			throw("出力のどの行もマッチしませんでした。");
		}
		
		// マッチ結果を返す
		return match_result;
	}
	
	// 通常は出力の配列をそのまま返す
	return output_arr;
}

このライブラリの利用例として,「サブフォルダをすべて再帰的に探索」とか「ファイルの連続コピー」などが挙げられる。

コマンドのほうがオプション変更などの融通が利きやすく,実装が早いのだ。

Windowsで「kakasi」のコマンドを使い,日本語文章を単語に分解,ローマ字変換する方法 (kakasi形態素解析するWindowsバッチのサンプルコード)
http://d.hatena.ne.jp/language_and_engineering/20150109/KakasiOnWindowsUsageSample


Windowsバッチで,iTunesのプレイリストを読み書きして,音楽ファイルをまとめて抽出する方法 (WSH/JScriptiTunesを自動操作)
http://d.hatena.ne.jp/language_and_engineering/20150114/iTunesAutomationByWSH


Windowsバッチ・コマンドで,大量のPDFを一括で結合・分割し,各ページ数を調べて一定サイズにまとめよう (pdftkをコマンドプロンプトWSHから使う方法)
http://d.hatena.ne.jp/language_and_engineering/20150113/PdftkOnWindowsBatch

今後の課題:

  • 実行したコマンドが失敗した場合はスクリプトの実行をそこで停める,などの検出オプションがあってもよい。コマンドのエラーコードを拾う。
  • log()もよく使うので,ロギングフレームワークになるとよいかも。ログ出力時の時刻など。