Ruby
Haskell
Windows
Gauche
batch

Windowsでshebangもどき、またはバッチにスクリプトを埋め込む方法

一覧

以下の例はすべて、各言語のインタプリタにパスが通っていることを前提としています。

Rubyの例.rb.bat
@ruby -x "%~f0" %*
@exit /b
#!ruby
## 以下スクリプトの内容 ##
puts "foo"
Perlの例.pl.bat
@perl -x "%~f0" %*
@exit /b
#!perl
## 以下スクリプトの内容 ##
print "foo\n";
Pythonの例.py.bat
@python -x "%~f0" %* & exit /b
## 以下スクリプトの内容 ##
print("foo")
nodeの例.js.bat
@node -e "require('fs').readFileSync(process.argv[1],'utf-8').match(/\x2f\x2f[^]*/); require('vm').runInThisContext(RegExp.lastMatch, {filename: process.argv[1], lineOffset: 2})" "%~f0" %*
@exit /b
// 以下スクリプトの内容 //
console.log("foo")
JScriptの例.js.bat
@if(0)==(0) ECHO OFF
cscript //NoLogo //E:JScript "%~f0" %*
exit /b
@end
// 以下スクリプトの内容 //
WScript.stdout.writeline("foo")
Luaの例.lua.bat
@lua -e "_,_,b=io.input([[%~f0]]):read('*l','*l','*a');assert(loadstring('\n'..b,[[%~f0]]))()"
@exit /b
-- 以下スクリプトの内容 --
print("foo")
GHCの例.lhs.bat
@runghc --ghc-arg=-x --ghc-arg=lhs "%~f0" %*
@exit /b
\begin{code}
-- 以下スクリプトの内容 --
main = print "foo"
\end{code}
Gaucheの例.scm.bat
;@gosh -x "%~f0" %* & exit /b
;;; 以下スクリプトの内容 ;;;
(display 'foo)
PowerShellの例.ps1.bat
@powershell -NoProfile -ExecutionPolicy RemoteSigned "&([ScriptBlock]::Create((cat -encoding utf8 \"%~f0\" | ? {$_.ReadCount -gt 2}) -join \"`n\"))" %*
@exit /b
## 以下スクリプトの内容 ##
echo foo

上の一覧にない場合は、汎用的な方法 をどうぞ。

解説

rubyやperl製のスクリプトは、パスを通せばコマンドプロンプトで通常のexeツールのように使うことができます。ただし、 hoge.rb のように拡張子を含めて打ちこむ必要があります。これを省略するにはPATHEXTをいじるのが簡単です。

しかし、スクリプトファイルをすでにインタプリタでなくエディタなどに関連付けしている場合はこの方法は使えません。

そこで、スクリプトを標準でPATHEXTに含まれているバッチファイルにラッピングします。つまり、.rb.pl.pyなどの拡張子を.batに変えて無理やり使うということです。

当然、スクリプトの拡張子を.batに変えただけでは動きません。コマンドプロンプトはスクリプトのことは知らず、普通にバッチファイルとして実行するためです。処理系を起動し、埋め込んだスクリプトを実行させる内容のバッチが必要になります。

コマンドプロンプトから見ると、このファイルはバッチファイルの末尾にゴミがあるように見えます。

スクリプト処理系から見ると、このファイルはスクリプトの先頭にゴミがあるように見えます。

それぞれに対して対策して、両方でエラーが起きないようにしたものが冒頭のサンプルたちです。

共通のテクニック

バッチファイルでは、以下のものを利用します。(コマンドプロンプトのhelp call参照)。

  • "%~f0" はシェルでいう$0に相当し、バッチファイル自身のフルパスに展開されます。
  • @ はバッチファイル内で実行したコマンドのエコーバックを抑制します。
  • exit /b はバッチファイルの実行を終了します。そのまま実行が続くと埋め込んだスクリプト本体までバッチファイルとして実行されるので、それを阻止します。

スクリプト側では、以下のどれかを利用します(楽な順)。

  1. 処理系にある、スクリプト以外の部分を無視するオプションを使う
  2. バッチの冒頭部分をコメントや文字列に包み、エラーにならないようにする
  3. 標準入力からスクリプトを読んで実行する
  4. 普通にファイルを読んで実行する

ruby (タイプ1)

rubyの-xオプション#!の行に当たるまでを無視するので、一行目をバッチとして書くことができます。

perl (タイプ1)

rubyと同様です。

また、PerlとRubyは頭だけでなく__END__を使ってスクリプトの終わりを明示できるので、exitの代わりにgoto を使うことで複数のRubyスクリプトを内蔵することができます(使い道はともかく)。

python (タイプ1)

Pythonの-x#!を探さず一行目を飛ばすだけなので、#!を書く必要はありません(書いてもかまいません)。

GHC (タイプ2)

runghcはPOSIXの場合同様 Literate Haskell を使うことでバッチ部分を迂回できます。

GHCiで読む際はghci -x lhs hoge.batのようにすると読み込めます。

Gauche (タイプ2)

Gaucheは#!のみを見るので、バッチファイルの @ を無視してくれません。

追記: しかし、; と組み合わせることで迂回することが出来ます。 これはGaucheの文法では行コメントとして解釈される一方、コマンドプロンプトの文法では(おそらく)空白として解釈されて無害になります。anohanaさんよりコメントで教えていただきました。ありがとうございました。

Lua (タイプ2)

hymkorさんの記事で、Lua5.2のラベル構文とコメント文を組み合わせて埋め込みに成功されました。その後、zetamattaさんがLua5.1に対応し、かつ短い方法を発見されたので、冒頭にはこちらを載せています。

JScript(WSHのJavaScript) (タイプ2)

JScript でハマる日々 - m2 を参照。@if文なるものがあり、それを使っています。このテクニックを発見されたのはおそらく ウィンドウズスクリプトプログラマ さんです。こちらもzetamatta さんに教えていただきました。ありがとうございました。

汎用的な方法

shebangに対応する機能のない言語は、バッチファイル内に埋め込めないことが多いです。

その場合は、以下のような方法があります。

ワンライナー

@インタプリタ -e "スクリプトの内容" %*

短いスクリプトなら-eオプションを使う方がコンパクトにすみます。
Ruby, Perl, Python, Gauche, GHCいずれでも使えます。

Goの場合は gore というツールが使えます。

標準入力から読んで実行 (タイプ3)

Perlなど上で挙がった多くのスクリプト処理系は、ファイル名を - とすることで標準入力からスクリプトを読んで実行することができます。-x に比べると $0 が設定されないのが玉に瑕ですが、コメント文とトリッキーに闘わずともすみます。mattnさんに教えていただきました。ありがとうございました。

一時ファイルを使う (タイプ4)

バッチ内に埋め込んだスクリプトを一旦一時ファイルに書き出し、それを実行します。

一時ファイルへの出力が必要ですが、理論上どんな言語にも適用できます。

JScript.NET

(長くなるので別記事に分けます)

Rhino

一行コメントを使った例です。jrunscript -l js -fの部分をcscriptv8nodeに置き換えれば他のJavaScriptエンジンでも動かせます。また、一行コメントのある他の言語(coffeescriptやbashの#、 VBScriptの'など)にも応用できそうです。

コメントでtatesukeさんに教えていただきました。ありがとうございました。元の例

rhinoの例.js.bat
@SET /P X=/* < NUL > "%~dp0_TEMP_%~n0" & TYPE "%~f0" >> "%~dp0_TEMP_%~n0" & jrunscript -l js -f "%~dp0_TEMP_%~n0" %* & DEL "%~dp0_TEMP_%~n0"  & EXIT /B & REM */
// 以下スクリプトの内容

おわりに

そもそもコマンド名補完がないから拡張子を邪魔に感じるわけで、PowerShellなりcygwinなりnyaosなりを使えばすむ話ですが他にshebangできそうな言語があれば教えてください。