Lua スクリプトと Windows のバッチファイルを同じファイルに同居させる

スクリプト言語で書いたプログラムをシェルコマンドとして実行したい時、 Unix の場合は shebang と呼ばれる行を書けば良い。

しかし、 Windows のコマンドインタープリター (cmd.exe) の場合はそういう風にはいかない。代わりに、バッチファイル (.bat) なり、バイナリの実行ファイル (.exe) を用意して、その中でスクリプト言語のインタープリターを呼び出す、という手法を使うことになる。

この場合、配布するファイルがバッチファイルと実行したいスクリプトファイルの2つになり、単独の実行ファイルを配布する場合に比べて2倍面倒である。1つのファイルを、バッチファイルとしてもスクリプトファイルとしても実行できれば良いのではないか。(このような、複数のプログラミング言語で解釈できるプログラムを polyglot と呼ぶことがあるようだ)

今回、対象とするスクリプト言語は Lua である。Lua スクリプトとバッチファイルの双方として実行可能なファイルを作りたい。

方針としては、

  • 最初の1行は、バッチファイル的にはコメントとなり、 Lua 的にはブロックコメントの開始部分になるようにする。
  • 2行目以降にバッチファイルとしての処理を書く(この部分は Lua としてはブロックコメントの中になる)。具体的には、自身を引数として Lua インタープリターを起動し、その後バッチファイルとしての実行を終了する(バッチファイルとして終了してしまえば、以降に Lua コードが書かれていようが関係ない)
  • その後は、ブロックコメントを解除して、 Lua スクリプトの本体を書く。

という感じになる。

まず、バッチファイルの文法について簡単におさらいしておく。

バッチファイルは行志向であり、コメントとしては行の先頭に rem と書くか、コロン : を置けば良い。後者は正確にはコメントではなくラベルであるが、コロン2つを並べてコメントとして使うということは非常にしばしば行われる。

バッチファイル中で自分自身のファイル名(フルパス)を参照したい時は %~f0 と書けば良い。(ただし、呼び出し時のコマンドにディレクトリ部分が含まれていると、拡張子が除外されてしまうようだ)

バッチファイルの実行を終了するには、 goto :eof と書けば良い。

基本的に、実行するコマンドはプロンプトに入力したのと同じように表示(エコー)されるが、コマンド行の先頭にアットマーク @ を置くか、 echo off を実行することによって抑制できる(バッチファイルの先頭に @echo off と書くのは今更説明するまでもないお約束である)。なお、ラベル行はいずれにせよエコーされないようだ。

次に、 Lua の文法である。

ブロックコメントは --[[ で始めて ]] で終わる。間にイコール = を好きな個数挟んで --[===[]===] という形でも良い。ブロックコメントのネストはイコールの個数を変えることによって行う。

最初の行では、バッチファイルのコメントの文法を使いつつ、正当な Lua コードとしても解釈できることが求められる。1つのやり方としては rem = rem と書くという方法があるが、バッチファイルの実行時に rem 行がエコーされてしまう。

別のやり方としては、Lua 5.2 で導入されたラベルの文法を使う。Lua 5.2 では識別子をコロン2つ :: で囲めばラベルになる。すでに書いたように、バッチファイルとして実行した場合はコロンから始まる行はエコーされない。

能書きはここまでにして、結論を書こう。

Lua 5.2 以降では、次のようにしてバッチファイルと Lua スクリプトを同居させることができる:

::dummy:: --[[
@lua "%~f0" %*
@goto :eof
]]
print("Hello from Lua!")

Lua 5.1 の場合は Lua のラベルの文法がないので、次のように書く必要がある(この書き方は Lua 5.2 以降でも通用する):

rem = rem --[[
@lua "%~f0" %*
@goto :eof
]]
print("Hello from Lua!")

バッチファイルとしての実行時に最初の行がエコーされない分、前者(要 Lua 5.2)の方が優秀である。

参考:


コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です