hack のためのネタ帳, etc,,,

やりたい事

WSH (Windows Script Host) のスクリプトを Windows 上の UNIX 環境である Cygwin や WSL から直接実行したい。
例えば
$ ./hello.js
だとか
$ ./hello.vbs
みたいなことがしたい。

BAT ファイルでそういうことを行う方法がある事は、前々から把握してて例えば、
WSH batch file embed とか「バッチファイル WSH 埋め込み」とでググると参考になるページが幾つか見つかる。
基本的に、ファイル名が *.bat のバッチファイルである必要があるが、

hello.js.bat

@if(0)==(0) ECHO OFF
cscript //NoLogo //E:JScript "%~f0" %* & EXIT /B %ERRORLEVEL%
@end

WScript.Echo("hello");

hello.vbs.bat

::'<SUB>@cscript //NoLogo //E:VBScript "%~f0" & EXIT /B %ERRORLEVEL%
' 上記の "<SUB>" はファイル終端コード ^Z(= 0x1A) の意味なので、何らかの方法で ^Z を得て適当に置換すること

WScript.Echo "hello"
というような方法がある。

更に、WSF (Windows Script File) と呼ばれる XML 形式のフォーマットに Job を定義して WSH の JScript や VBAScript を記述する方法を用いると、1 つのスクリプトに BAT ファイル、JScript、VBAScript を混在させた形で複数の Job を定義し、Job 内では互いのスクリプト間で関数を呼び合う事すら可能な、以下のような方法も見つけた。

hello.wsf.bat

<!-- : Batch File
@ECHO OFF
ECHO hello with Batch File
cscript //NoLogo "%~f0?.wsf" //Job:job1
cscript //NoLogo "%~f0?.wsf" //Job:job2
cscript //NoLogo "%~f0?.wsf" //Job:job3
cscript //NoLogo "%~f0?.wsf" //Job:job4
EXIT /B %ERRORLEVEL%
----- WSF Script --->
<package>
  <job id="job1">
    <script language="VBScript">
      WScript.Echo "hello with VBScript"
    </script>
  </job>
  <job id="job2">
    <script language="JScript">
      WScript.Echo("hello with JScript");
    </script>
  </job>
  <job id="job3">
    <script language="JScript">
      function hello_js() {
        WScript.Echo("hello with JScript in job3");
      }
    </script>
    <script language="VBScript">
      Sub hello_vbs()
        WScript.Echo "hello with VBScript in job3"
      End Sub
    </script>
    <script language="JScript">
      hello_js();
      hello_vbs();
    </script>
  </job>
  <job id="job4">
    <script language="JScript">
      function hello_js() {
        WScript.Echo("hello with JScript in job4");
      }
    </script>
    <script language="VBScript">
      Sub hello_vbs()
        WScript.Echo "hello with VBScript in job4"
      End Sub
    </script>
    <script language="VBScript">
      call hello_js
      call hello_vbs
    </script>
  </job>
</package>

JScript または VBScript 単独でシンプルに書くとこんな感じ。

hello.wsf.js.bat

<!-- :
@cscript //NoLogo "%~f0?.wsf" %* & @EXIT /B %ERRORLEVEL% 
--><job><script language="JScript">

WScript.Echo("hello");

</script></job>

hello.wsf.vbs.bat

<!-- :
@cscript //NoLogo "%~f0?.wsf" %* & @EXIT /B %ERRORLEVEL% 
--><job><script language="VBScript">

WScript.Echo "hello"

</script></job>

Shebang 代わりの前段が少々長いのと、末尾のタグが必須なのがちょっと面倒臭いが、これはなかなか使い勝手が良い。

本題

さて、Cygwin や WSL から *.js や *.vbs を cscript.exe に食わせる方法なのだが、
ふと Cygwin や WSL では少なくとも bash の場合、実行ビットが立ってて shebang がないと、shell script 扱いして bash に食わしてる事に気付いた。

んで、BAT ファイルに WSH を埋め込む方法は基本的にバッチファイルの頭の部分を WSH のコメントに見せかけつつ、cmd.exe のコマンドとしては有効な命令とすることで、その中から cscript を呼び出すテクニックなんだけど、んじゃぁ 1 行目を WSH にコメント扱いさせて、bash 的に実行可能な構文にしてやれば、shebang 代わりになるじゃないかという事に気付いてしまったわけである。
つまり、JScript の場合は、// から始まるコマンドの絶対パス、VBScript の場合は '...' で囲ったコマンド名または '' からコマンドを始めればよいということになる。

ただし、Cygwin の場合、// で始めると UNC 扱いされてしまうため問題がある。
試してみたところ、これは、/// とすると普通にフルパスとして扱えるという事が確認出来た。

という事で、上記の思い付きを実装してみたところ、以下のようにすることで目出度く目的を達することが出来た。

hello.js.sh

///usr/bin/env cscript.exe //NoLogo "$0" "$@"; exit $?

WScript.Echo("hello");

hello.vbs.sh

''/usr/bin/env cscript.exe //NoLogo "$0" "$@"; exit $?

WScript.Echo "hello"

ただし、このままだと、"$0" のパスが cscript.exe に解釈不能な場合があるので、cygpath, wslpath を用いて UNIX 環境 → win32 環境の path 変換をする wrapper を作り /usr/bin/env の代わりに使う方が良いと思われる。
因みに、これを思い付いたのは、WSH Wrapper なる bash script をほぼ書き終えた直後で、書いてたものがなんか無駄になってしまった感があるんだけど、割と応用は効きそうだし、上記のパス変換の問題もあるので、後日暇を見て調整してから公開したい。

以上、結果的に bash script をベースに WSH を埋め込んだのハイブリッドスクリプトが書けるねという話になった。

なお、上記ではファイル名を *.sh としているが、バッチファイルと違って、実行ビットさえ立ってればファイル名は自由なので、*.sh はおろか *.js や *.vbs すら付与する必要はないので念のため。

追記:
cygpath, wslpath への簡易対応してみた。

hello.js.sh

///bin/true;w=( $(type -p cygpath wslpath) );cscript.exe //NoLogo "$("$w" -w "$0")" "$@";exit $?

WScript.Echo("hello");

hello.vbs.sh

'':;w=( $(type -p cygpath wslpath) );cscript.exe //NoLogo "$("$w" -w "$0")" "$@";exit $?

WScript.Echo "hello"

あと、改行コードは CR LF (0x0d 0x0a) だと動かないので LF (0x0a) にする事。

関連

コメントをかく


「http://」を含む投稿は禁止されています。

利用規約をご確認のうえご記入下さい

Wiki内検索

フリーエリア

管理人/副管理人のみ編集できます