chkconfig.js2008年06月15日 06時19分04秒

またくだらんモノ作ったなぁ

くだらんと思いつつも、chkconfig風にWindowsサービスの起動方法を確認したり変更したりするユーティリティスクリプト作ってみました。

いや、前回のservice.cmdの引数に渡す「サービス名」を確認するのが主な目的だったんだけど、起動方法の変更とかもなんとなくできそうだったのでやってみたらできたってことで。

ソース

こんな感じ。例によってNYSLで。ダウンロードはこちらから。

var fso = new ActiveXObject("Scripting.FileSystemObject");
var shell = new ActiveXObject("WScript.Shell");

// コマンドライン引数の取得
var args = new function() {
  var args = WSH.Arguments, result = [];
  for(var col = new Enumerator( args ); ! col.atEnd(); col.moveNext()) {
    result[ result.length ] = String( col.item() );
  }
  var named = {};
  for(var col = new Enumerator( args.Named ); ! col.atEnd(); col.moveNext()) {
    named[ String( col.item() ) ] = String( args.Named.Item( col.item() ) );
  }
  var unnamed = [];
  for(var col = new Enumerator( args.Unnamed ); ! col.atEnd(); col.moveNext()) {
    unnamed[ unnamed.length ] = String( col.item() );
  }
  result.named = named;
  result.unnamed = unnamed;
  
  result.toArray = function() {
    var result = [];
    for(var i = 0, l = this.length; i < l; i++) {
      result[ i ] = /((^".*"$)|(^'.*'$))/.test( this[i] ) ?
        this[i] : [ '"', this[i], '"' ].join("");
    }
    return result;
  };
  result.toString = function() {
    return this.toArray().join(" ");
  };
  return result;
}();

// cscriptで強制起動
if( /wscript\.exe$/i.test( WSH.FullName ) ) {
  shell.Run( [
    "cscript",
    /((^".*"$)|(^'.*'$))/.test( WSH.ScriptFullName ) ? WSH.ScriptFullName : [ '"', WSH.ScriptFullName, '"' ].join(""),
    args
  ].join(" ") );
  WSH.Quit();
}

// ユーティリティ関数定義
var echo = function(s) {
  print( [ s, "\n" ].join("") );
}
var print = function(s) {
  WSH.StdOut.Write( s || "" );
}
var input = function() {
  if( arguments[0] ) print( arguments[0] );
  print( ">" );
  return WSH.StdIn.ReadLine();
}

Error.prototype.toString = function() {
  return this.description || this.message || this.number || this;
};

var $break = {};
var $continue = {};
Enumerator.prototype.each = function(iterator) {
  try {
    var i = 0;
    for(this.moveFirst(); ! this.atEnd(); this.moveNext()) {
      try {
        iterator( this.item(), i++ );
      } catch(e) {
        if( e != $continue ) throw e;
      }
    }
  } catch(e) {
    if( e != $break ) throw e;
  }
};

String.prototype.repeat = function(count) {
  var buf = [];
  for(var i = 0; i < count; i++) buf[buf.length] = this;
  return buf.join("");
};
var SvcMgr = function() {
  this.wmi = new ActiveXObject("WbemScripting.SWbemLocator").ConnectServer();
};
SvcMgr.prototype = {
  list : function(name) {
    var query = "SELECT Name, DisplayName, Description, StartMode, State FROM Win32_Service";
    if( name ) query += " WHERE Name = '" + name + "'";
    return new Enumerator( this.wmi.ExecQuery( query ) );
  },
  change : function(name, value) {
    this.list(name).each( function(service) {
      service.ChangeStartMode( value );
      throw $break;
    } );
  },
  exec : function(params) {
    if( params[0] && params[0].toLowerCase() == "--list" ) {
      // --list
      var max = 39;
      this.list( params[1] || false ).each( function(service) {
        echo( [
          service.Name.length < max ?
            ( service.Name + " ".repeat(max) ).substr(0, max) : service.Name,
          service.StartMode
        ].join(" ") );
      } );
    } else if( params[0] && params[0].toLowerCase() == "--detail" && params[1] ) {
      // --detail
      this.list( params[1] ).each( function(service) {
        echo( "サービス名 : " + service.Name );
        echo( "表示名     : " + service.DisplayName );
        echo( "スタートアップの種類 : " + ( {
          "Boot" : "ブート",
          "System" : "システム",
          "Auto" : "自動",
          "Manual" : "手動",
          "Disabled" : "無効"
        }[ service.StartMode ] || service.StartMode ) );
        echo( "状態       : " + ( {
          "Running" : "実行中",
          "Stopped" : "停止中"
        }[ service.State ] || service.State ) );
        echo( "説明 : " );
        echo( "  " + ( service.Description || "(no description)" ) );
        echo( "" );
        throw $break;
      } );
    } else if( params[0] && ! /^--/.test(params[0]) && params[1] ) {
      // change startmode
      var mode = {
        "auto" : "automatic",
        "sys" : "system",
        "man" : "manual",
        "dis" : "disabled"
      }[ params[1] ] || params[1];
      this.change(params[0], mode);
      this.exec( [ "--list", params[0] ] );
    } else {
      echo( "使い方 : [cscript | wscript] chkconfig.js --list [サービス名]" );
      echo( "         [cscript | wscript] chkconfig.js --detail サービス名" );
      echo( "         [cscript | wscript] chkconfig.js サービス名 [ Boot |" );
      echo( "                                                       System |" );
      echo( "                                                       Automatic |" );
      echo( "                                                       Manual |" );
      echo( "                                                       Disabled ]" );
      echo();
    }
  }
};

new SvcMgr().exec( args.unnamed );

これをcscript経由で起動するバッチファイルを%SystemRoot%にでも入れておけばよいかと。

使い方

そのまま起動すると使い方は表示されるのでだいたいわかると思いますが、以下の3種類の引数を受け付けます。

  1. --list [サービス名]
  2. <サービス名> <起動方法>
  3. --detail <サービス名>

--list [サービス名]

「--list」はサービスの起動方法を確認します。こんな感じ。

C:\Documents and Settings\dara-j>cscript chkconfig.js --list
AcPrfMgrSvc                             Auto
AcSvc                                   Disabled
Alerter                                 Disabled
ALG                                     Disabled
AppMgmt                                 Manual
aspnet_state                            Manual
AudioSrv                                Auto
  :
 (中略)
  :
WudfSvc                                 Manual
WZCSVC                                  Auto
xmlprov                                 Manual

C:\Documents and Settings\dara-j>

各行はサービス名と起動方法が出力されます。

ここに表示されるサービス名は管理ツールの「サービス」で表示される「表示名」ではなく、内部のサービス名で、前回のservice.cmdに(というか、net startやnet stopに)渡すサービス名はこれになります。

--listの後ろにサービス名をつけると、そのサービスの情報のみが表示されます。

C:\Documents and Settings\dara-j>cscript chkconfig.js --list iisadmin
IISADMIN                                Auto

C:\Documents and Settings\dara-j>

また、パイプでfindstrにつないでもいいかもしれません。
C:\Documents and Settings\dara-j>cscript chkconfig.js --list | findstr -i vnc
winvnc                                  Auto

C:\Documents and Settings\dara-j>

findstrは-i(Ignore Case)がよいでしょう。サービス名うろ覚え時にぜひ。

<サービス名> <起動方法>

サービスの起動方法を変更します。Linuxのようなランレベルの概念はないため、--level <levels>のようなパラメータは必要なく、on/off/resetの代わりに「Boot」「System」「Automatic」「Manual」「Disabled」の5種類の指定が可能です(が、「Boot」と「System」はよくわかりません。指定しても反映されてるやらされてないやら)。

実行すると、設定反映後の状態を出力します。

C:\Documents and Settings\dara-j>cscript chkconfig.js w3svc disabled
W3SVC                                   Disabled

C:\Documents and Settings\dara-j>cscript chkconfig.js w3svc automatic
W3SVC                                   Auto

C:\Documents and Settings\dara-j>

ちなみに大文字小文字は区別されません。

--detail <サービス名>

これは本家chkconfigには相当する機能はないのですが、オマケで実装してみました。指定サービスの詳細を表示します。

C:\Documents and Settings\dara-j>cscript chkconfig.js --detail iisadmin
サービス名 : IISADMIN
表示名     : IIS Admin
スタートアップの種類 : 自動
状態       : 実行中
説明 :
  インターネット インフォメーション サービスのスナップインを使用した Web と FTP
サービスの管理を提供します。

Enter キーで終了します>

C:\Documents and Settings\dara-j>

ちょっとした解説

たいしたことはしてないのですが、WMIでWin32_Serviceクラスを扱っています。サービスの列挙は

SELECT * FROM Win32_Service
ってのが基本で(実際は取得するプロパティを限定してますが)、WHERE句でサービスを限定したり、返ってきたインスタンスに対してChangeStartModeメソッドを実行して起動方法を変えたりしています。

参考リンク

バッチファイルでserviceコマンドもどき2008年06月14日 03時45分25秒

Windowsのバッチファイルでlinuxのserviceコマンドもどきを作るというバカなことを試みてみたり。誰かすでにやっていそうだけど。

@echo off

rem 'restart' の場合
if "%2" == "restart" goto RESTART

rem 'start' または 'stop' の場合
if "%2" == "start" goto EXEC
if "%2" == "stop" goto EXEC

rem それ以外の場合は使い方を表示
echo このコマンドの構文は次のとおりです:
echo;
echo;
echo SERVICE [ START ^| STOP ^| RESTART ] [サービス]
echo;
goto END

:EXEC
net %2 %1
goto END

:RESTART
net stop %1
net start %1

:END

これを「service.cmd」とでもして%SystemRoot%とか、パスが通ってるとこに放り込めばOK。

基本は「NET START」や「NET STOP」を、引数の順序を入れ替えてるだけなんだけど、「NET RESTART」ってのがなかったのでそれっぽい実装にしてみている。

いや、なんとなくOpenSSH for Winなんか入れてみて、環境いじったりするのにサービスの再起動をちょろちょろとやる必要があったもんで。

wget.jsを修正しました2008年06月13日 21時12分46秒

保存先に同名ファイルがあった場合に上書き保存に失敗するというなさけないバグがあったので、あわてて修正しました。元記事のソース・ダウンロード用ソースとも修正ずみです。

wget.js2008年06月11日 03時54分48秒

wget.js

まったくもってくだらんと思うが、コマンドライン引数でURL渡すとローカルに保存するコンソールスクリプト。なんで作ろうとしたのか忘れたけど、なんか作ってみたので載せてく。

ソース

※:同じローカルファイル名で上書きできないバグを修正しました(08.06.13)

※:この記事のコメント欄でhATrayfloodさんからContent-Disposition対応ソースのURLを教えていただきました。ソースはこちら(JSファイル直リン)。(11.08.25)

以下のソースをコピーして「wget.js」とでもつけてローカルに保存するか、これを名前をつけて保存。

依存ライブラリは一切なし。ライセンスはNYSLね。

var fso = new ActiveXObject("Scripting.FileSystemObject");
var shell = new ActiveXObject("WScript.Shell");

// コマンドライン引数の取得
var args = new function() {
  var args = WSH.Arguments, result = [];
  for(var col = new Enumerator( args ); ! col.atEnd(); col.moveNext()) {
    result[ result.length ] = String( col.item() );
  }
  var named = {};
  for(var col = new Enumerator( args.Named ); ! col.atEnd(); col.moveNext()) {
    named[ String( col.item() ) ] = String( args.Named.Item( col.item() ) );
  }
  var unnamed = [];
  for(var col = new Enumerator( args.Unnamed ); ! col.atEnd(); col.moveNext()) {
    unnamed[ unnamed.length ] = String( col.item() );
  }
  result.named = named;
  result.unnamed = unnamed;
  
  result.toArray = function() {
    var result = [];
    for(var i = 0, l = this.length; i < l; i++) {
      result[ i ] = /((^".*"$)|(^'.*'$))/.test( this[i] ) ?
        this[i] : [ '"', this[i], '"' ].join("");
    }
    return result;
  };
  result.toString = function() {
    return this.toArray().join(" ");
  };
  return result;
}();

// cscriptで強制起動
if( /wscript\.exe$/i.test( WSH.FullName ) ) {
  shell.Run( [
    "cscript",
    /((^".*"$)|(^'.*'$))/.test( WSH.ScriptFullName ) ? WSH.ScriptFullName : [ '"', WSH.ScriptFullName, '"' ].join(""),
    args
  ].join(" ") );
  WSH.Quit();
}

// ユーティリティ関数定義
var echo = function(s) {
  print( [ s, "\n" ].join("") );
}
var print = function(s) {
  WSH.StdOut.Write( s || "" );
}
var input = function() {
  if( arguments[0] ) print( arguments[0] );
  print( ">" );
  return WSH.StdIn.ReadLine();
}
var nameFromUrl = function(url) {
  var parts = url.split( /[\\\/]/g );
  var f = parts[parts.length - 1];
  f = f.replace( /[\\\/:,;\*\?"<>\|]/g, "_" );
  parts[parts.length - 1] = f;
  return f;
}

Error.prototype.toString = function() {
  return this.description || this.message || this.number || this;
}

if( ! args.length || /^\/(\?|(help))/i.test( args[0] ) ) {
  // 引数がないか、ヘルプスイッチが指定された場合は使い方を表示して終了
  echo( "使い方 : [cscript | wscript] wget.js [ ]" );
  echo( "オプション : " );
  echo( "        アクセスするインターネットリソースのURL" );
  echo( "  保存先ファイルパス。省略時はURLのファイル名と" );
  echo( "             同じ名前でカレントディレクトリに保存" );
  echo();
  input( "Enter キーで終了します" );
} else {
  // メイン処理
  var url = args.unnamed[0], fileName = args.unnamed[1] || nameFromUrl(url);

  var xhr = new ActiveXObject("Microsoft.XMLHTTP");
  xhr.open( "get", url, true );
  var completed = false;
  xhr.onreadystatechange = function() {
    print( "." );
    if( xhr.readyState < 4 ) return;
    echo();
    try {
      var headers = (function(headers) {
        var result = {}, lines = headers.split( /((\r\n)|\r|\n)/g );
        for(var i = 0, l = lines.length; i < l; i++) {
          var matches = /^([^:]+): (.*)$/.exec( lines[i] );
          if( matches ) result[ matches[1].toLowerCase() ] = matches[2];
        }
        return result;
      })( xhr.getAllResponseHeaders() );
      echo( [ "ファイルサイズ : ", headers[ "content-length" ] || "不明" ].join("") );
      
      var stream = new ActiveXObject("ADODB.Stream");
      stream.Open();
      stream.Position = 0;
      stream.Type = 1;
      stream.Write( xhr.responseBody );
      stream.SaveToFile( fileName, 2 );  // adSaveCreateOverWrite
      echo( "完了" );
      
    } catch(e) {
      echo( e );
    } finally {
      if( stream ) {
        try { stream.Close(); } catch(ie) {}
      }
    }
    completed = true;
  }
  print( [ "url '", url, "' に接続中..." ].join("") );
  xhr.send();
  while( ! completed ) {
    WSH.Sleep( 50 );
  }
}

使い方

コマンドラインからcscript経由で使う。別にwscriptでもいいけど。

んで、カレントにwget.jsがあるとして、「http://server.domain/path/to/file.zip」をダウンロードするには

C:\wget_js>cscript wget.js http://server.domain/path/to/file.zip
Microsoft (R) Windows Script Host Version 5.6
Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.

url 'http://server.domain/path/to/file.zip' に接続中...
ファイルサイズ:1024
完了

C:\wget_js>

なんて感じ。

保存時のファイル名を指定したい場合は第二引数を使う。

C:\wget_js>cscript wget.js http://server.domain/path/to/file.zip myfile.zip
Microsoft (R) Windows Script Host Version 5.6
Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.

url 'http://server.domain/path/to/file.zip' に接続中...
ファイルサイズ:1024
完了

C:\wget_js>

て具合。保存先は当然パス指定もOK。

あとは

@echo off
cscript //nologo C:\wget_js\wget.js %*

みたいなバッチファイルを%SystemRoot%にでも入れとけばそこはかとなくwget風。

仕組みとか注意点とか

Microsoft.XMLHTTPでURLにアクセスして、受信したresponseBodyをそのままADODB.Streamに喰わせて保存してるだけ。

こんな乱暴なアプローチなので、いきおいすべてのデータをメモリ上で取り扱う構造なので、大きいファイルをこれで落とそうなんてしないほうがよいかと。あと、ftpでコケたときあったなー。XHRがエラー吐きやがるの。

あ、あとURLにfile://~とかやるとコピーコマンド代わりになるとか。まったく意味ないけど。

作ったはいいけど、自分でも使わなそうだなー。

インタラクティブなJScriptコンソール2008年06月02日 02時33分25秒

久々にJSでも書いてみるか。

気がついたら1ヶ月近くもブログ放置状態だったので、ちょっと前になんとなく作ったスクリプトをば。

もうとっくに誰かがやってるであろう(つか、自分で昔作ってるし)、対話型JScriptシェル(っていうのか?)なんですが、ライブラリのロード機構とドラッグドロップでのバッチ処理をサポートしてるのでライブラリの整備次第ではちょっとしたデータ処理なんかに使えるのでは、と。

いきなりソース

以下、本体の全ソース。「jsi.js」とでもしてローカルに保存してください。例によってNYSLで。

var Global = {
  fso : new ActiveXObject("Scripting.FileSystemObject"),
  shell : new ActiveXObject("WScript.Shell"),
  arguments : (function() {
    var result = {
      raw : [],
      named : {},
      unnamed : [],
      toArray : function() {
        var result = [];
        for(var i = 0, l = this.raw.length; i < l; i++) {
          result.push( '"' + this.raw[i] + '"' );
        }
        return result;
      }
    };
    for(var list = new Enumerator(WSH.Arguments); ! list.atEnd(); list.moveNext()) {
      result.raw.push( String(list.item()) );
    }
    for(var list = new Enumerator(WSH.Arguments.Unnamed); ! list.atEnd(); list.moveNext()) {
      result.unnamed.push( String(list.item()) );
    }
    for(var list = new Enumerator(WSH.Arguments.Named); ! list.atEnd(); list.moveNext()) {
      result.named[ list.item() ] = String( WSH.Arguments.Named.item( list.item() ) );
    }
    return result;
  })(),
  fullName : WSH.ScriptFullName,
  startupPath : (function() {
    var fso = new ActiveXObject("Scripting.FileSystemObject");
    return fso.GetFile( WSH.ScriptFullName ).ParentFolder.Path;
  })(),
  buildPath : function(path1, path2, separator) {
    separator = separator || "\\";
    var reg = new RegExp( separator + "$" );
    return [ path1, path2 ].join( reg.test( path1 ) ? "" : separator );
  },
  getTextContents : function(path) {
    try {
      var stream = Global.fso.OpenTextFile( path, 1 );
      return stream.ReadAll();
    } finally {
      if( stream ) stream.Close();
    }
  },
  echo : function() {
    for(var i = 0, l = arguments.length; i < l; i++) {
      Global.print( arguments[i] + "\n" );
    }
  },
  print : function(msg) {
    WSH.StdOut.Write(msg);
  },
  input : function(msg) {
    if( msg ) Global.print( msg + " > " );
    return WSH.StdIn.ReadLine();
  },
  registCurrentScript : function(script) {
    return this.__currentScript = script;
  },
  unregistCurrentScript : function() {
    this.__currentScript = null;
  },
  getCurrentScript : function() {
    return this.__currentScript || this.fullName;
  }
}

if( /wscript\.exe$/i.test( WSH.FullName ) ) {
  Global.shell.run( [
    "cscript",
    "\"" + WSH.ScriptfullName + "\""
  ].concat( Global.arguments.toArray() ).join(" ") );
  WSH.Quit();
}

Global.shell.CurrentDirectory = Global.startupPath;

Error.prototype.toString = function() {
  return [ this.description || this.message || "", " [", this.number, "]" ].join("");
};

var Runtime = function() {
  this.initialize.apply( this, arguments );
};

Runtime.prototype = {
  initialize : function() {
  },
  echo : function() {
    Global.echo.apply( Global, arguments );
    return this;
  },
  print : function() {
    Global.print.apply( Global, arguments );
    return this;
  },
  input : function() {
    return Global.input.apply( Global, arguments );
  },
  run : function(libs, args) {
    var _isBreak = false;
    var _, __sources__ = libs || [], __buf__ = [];
    if( args instanceof Array ) __sources__ = __sources__.concat( args );
    while(! _isBreak) {
      var echo = function() { Global.echo.apply( Global, arguments ); };
      var print = function() { Global.print.apply( Global, arguments ); };
      var input = function() { return Global.input.apply( Global, arguments ); };
      var exit = function() { _isBreak = true; };
      
      var __cmd__ = __sources__.length ?
        " " : this.input( __buf__.length ? ( "   " + ( __buf__.length + 1 ) ).slice(-3) : "jsi" );
      if( __cmd__.length ) {
        try {
          if( /^\./.test( __cmd__ ) && ! __sources__.length ) {
            if( /^\.load\s/.test( __cmd__ ) ) {
              __sources__ = __cmd__.replace( /^\.load\s+/, "" ).split( " " );
            } else if( /^\.quit.*$/.test( __cmd__ ) ) {
              exit();
            } else {
              throw new Error( "コマンド '" + __cmd__ + "' は定義されていません。" );
            }
          } else if( __sources__.length ) {
            with( { ___s : null } ) {
              while( __sources__.length ) {
                ___s = Global.registCurrentScript( __sources__.shift() );
                this.print( "'" + ___s + "' loading..." );
                try {
                  eval( Global.getTextContents( ___s ) );
                  this.echo( "done." );
                } catch(err) {
                  this.echo( "ERROR !! : ", err );
                }
                Global.unregistCurrentScript();
              }
            }
            __sources__ = [];
          } else {
            __buf__.push( __cmd__.replace( / _$/, "" ) );
            if( /;$/.test( __cmd__ ) ) {
              this.print( "--> " );
              _ = eval( __buf__.join("\n").replace( /;$/, "") );
              this.echo(
                _ != null ? _ : ( _ === undefined ? "(undefined)" : "(null)" ),
                ""
              );
              __buf__ = [];
            }
          }
        } catch(err) {
          this.echo( "ERROR !! : ", err );
          __buf__ = [];
        }
      }
    }
  }
};
with( {
  confPath : Global.buildPath( Global.startupPath, "libsettings" ),
  libs : null
} ) {
  try {
    if( Global.fso.FileExists( confPath ) ) {
      libs = Global.getTextContents( confPath ).split( /((\r\n)|\r|\n)/g );
    }
    new Runtime().run( libs, Global.arguments.unnamed );
  } catch(err) {
    Global.echo( err );
    Global.input("");
  }
}

一式ダウンロード

上記ソースのみで使えますが、ライブラリロードのサンプルなども含めた一式を以下からダウンロードできます。

添付のライブラリは以下のとおりです。以前このブログに載せたやつの寄せ集めみたいな感じですが。

  • dummy.js - prototype.jsをWSH環境で使用するためのダミーオブジェクト定義
  • prototype.js - 懐かしの1.5.1です。別に1.6使う意味もないので。
  • json.js - 2006-10-29版と、かなり古めです。使った限りではもっともパフォーマンスがよかったもので、個人的にずっと愛用しているもので。
  • format.js - NumberやDateの書式指定をサポートしたフォーマットライブラリです。使い方はリンク先を参照してください。
  • EnumeratorEx.js - JScriptのEnumeratorオブジェクトをprototype.jsのEnumerableに対応させるライブラリです。たいしたことしてませんが。
  • json_formatter.js - 整形エンコード専用のJSONライブラリです。こっそりパフォーマンス改善しています。微量ですが。
prototype.js、json.js以外はすべてNYSLとします。

使い方

上のソースを保存するか、jsi_v001.zipを適当なディレクトリに解凍し、jsi.jsをダブルクリックで起動してください。次のようなコンソールが起動します。

Microsoft (R) Windows Scripting Host Version 5.6
Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.

jsi >

で、適当にコードを書きます。末尾のセミコロンでステートメント区切りになります。

jsi > 1+1;
--> 2

jsi > "abc".replace(/[ab]/g, "_");
--> __c

jsi >

セミコロンが出現するまでは同一ステートメントとして認識されます。

jsi > (1 + 1)
  2 > * 3;
--> 6

jsi >

ブロックがネストしてたりで、終了前にセミコロンが登場する場合は行末に「 _」(半角スペースとアンダースコア)を入れると途中改行として扱われます。

jsi > $R(1, 10).each( function(i) {
  2 >   echo( i ); _
  3 > } );
--> 1
2
3
4
5
6
7
8
9
10
[object Object]

jsi >

最後に評価された結果は変数「_」(アンダースコア)に格納されます。

jsi > "ABC";
--> ABC

jsi > _;
--> ABC

jsi > _.length;
--> 3

jsi > _;
--> 3

jsi >

当然、変数も使えます。

jsi > var a = 1000;
--> (undefined)

jsi > var b = 200;
--> (undefined)

jsi > a * b;
--> 200000

jsi >

組み込み関数

コンソール入出力用に「echo」「print」「input」の3つの組み込み関数を提供しています。

echoは引数をコンソールへ出力します。末尾に必ず改行がつきます。(以下の例では評価結果「undefined」が改行後に出力されています)

jsi > echo( "abc" );
--> abc
(undefined)

jsi >

printも引数をコンソールへ出力しますが、自動改行はされません。

jsi > print( "abc" );
--> abc(undefined)

jsi >

inputはコンソールからの入力を受け取ります。

jsi > input();
--> aaa
aaa

jsi >

引数に指定した文字列をプロンプトとして出力することもできます。

jsi > input("入力してくれ");
--> 入力してくれ > 入力したった
入力したった

jsi >

特殊コマンド

通常のJSコードとは別にピリオドから始まる特殊コマンドを2つ定義しています。「.quit」と「.load」です。これらは末尾にセミコロンがなくても認識されます。

「.quit」はjsi環境を終了させます(以下の例はコマンドプロンプトから明示的にcscriptで起動した場合で、ダブルクリックでwscript起動した場合は即座にコンソールが終了します)。

jsi > .quit

C:\Documents and Settings\dara-j\jsi>

.loadは指定パスにあるファイルをJSコードとして評価します。ライブラリのロードやバッチ実行に利用できます。

例えば、

var array1 = [ "a", "b", "c" ];
var array2 = [ "D", "E", "F" ];
var array3 = array1.concat( array2 );
echo( array3.join("\n") );

なんてソースを「test.js」としてjsi.jsと同じディレクトリに設置して、これをバッチ起動する場合は以下のようにします。
jsi > .load test.js
a
b
c
D
E
F
jsi >

ロードするファイルパスは続けて指定でき、指定順に実行されます。

例えば「test2.js」を以下のように定義し、

echo( "test2.js" );
for(var i = 0; i < array3.length; i++) {
	array3[i] = array3[i].toUpperCase();
}
echo( array3.join("\n") );

test.js → test2.jsの順に実行するには以下のようにします。
jsi > .load test.js test2.js
a
b
c
D
E
F
test2.js
A
B
C
D
E
F
jsi >

ライブラリの自動ロード

jsi.jsと同じディレクトリに「libsettings」というファイルが設置されていると、その内容に応じて起動時に自動的にライブラリをロードします。

書式は非常に単純で、ライブラリのパスを行単位に記述するだけです。詳しくはjsi_v001.zipに添付の「libsettings」をテキストエディタで開いて参照してみてください。

バッチ処理

jsi.jsに.jsファイルをドラッグドロップすると、起動後にそのファイルを.load特殊コマンドで評価します。これによりバッチ処理が行えます。

先ほどのtest.jsなどをjsi.jsにドラッグドロップすると、コンソールから.loadしたのと同様に処理されます。複数のjsファイルをドラッグドロップすることも可能です。

以下、バッチ固有というわけでもありませんがちょっとしたTIPSを。

バッチスクリプト内で実行中スクリプト名を取得する

コードを見ればわかりますが、.loadでの評価はeval()なため、バッチスクリプト内で「WSH.ScriptFullName」とかしてもjsi.jsの情報しか取得できません。せめて自身のパスくらいは取得できるように、ちょっとした仕掛けを用意してあります。

jsi.jsでは「Global」というオブジェクトを定義しているのですが、.loadによるロード・評価時に、このオブジェクトに評価中スクリプトの情報を設定していおり、これをgetCurrentScript()メソッドで取得することができます。

print( "WSH.ScriptFullName = " );
echo( WSH.ScriptFullName );

print( "Global.getCurrentScript() = " );
echo( Global.getCurrentScript() );

をtest3.jsとすると、以下のようになります。
jsi > .load test3.js
WSH.ScriptFullName = C:\Documents and Settings\dara-j\jsi\jsi.js
Global.getCurrentScript() = test3.js
jsi >

上記は.loadでロードしたためtest3.jsのパスが相対パスになっていますが、ドラッグドロップ起動した場合はフルパスになります。

バッチスクリプトから終了

バッチスクリプトは実行が完了するとそのままコンソールの入力待ち状態になりますが、exit()を実行することで即座にjsi.jsそのものを終了させることができます。

確認をとってから自動終了させるには、input()で入力を受け付けた後にexit()すればOKです。

予定というか課題というか

現状ではライブラリのロードにfsoを使用しているため、ローカルファイルに限定されています。できればこれhttp対応したいなぁ。まぁライブラリでGlobal.getTextContents()をオーバーライドしちゃえば済むんですが。

また、「 _」で途中改行ってのはちとみっともないのでまともなステートメントブロックの検出ロジックを入れたいなぁ。んで、バッチスクリプトにコメントでライブラリロードさせると。

面倒くさいのでやらないかも。