ドラッグアンドドロップされたファイルをWSLのコマンドに渡すバッチファイル - その2

以前書いた記事の補足というかおまけというか。

suzusime-log.hatenablog.jp

前回の記事の「その他の方法」のところで、RubyPythonスクリプトドラッグアンドドロップで起動するためにはレジストリを弄るか2ファイルに分ける必要があると書いた。

しかし、1ファイルかつレジストリを弄ることなく、ドラッグアンドドロップされたファイルを引数に取るRuby等のスクリプトを起動する方法があることを知った。というわけで、この方法を追記しておく。

Rubyの場合

早速結論を示そう。

前回も取り上げた、ドラッグアンドドロップされたファイルをffmpegでmp3に変換するスクリプトRubyで書いたものが以下である。

chcp 65001
cd /d %~dp0
ruby -x %0 %*
exit /b 0

#!ruby
# 与えられた全入力ファイルに対してループ
ARGV.each do |infile|
  # ファイルのwslでのパスを取得
  infile_wsl = `wsl wslpath -a '#{infile.gsub(/\\/, '/')}'`.chomp
  # 拡張子をmp3に変えたものを出力ファイル名にする
  outfile_wsl = "#{File.dirname(infile_wsl)}/#{File.basename(infile_wsl, ".*")}.mp3"
  # ffmpegを実行
  system("wsl ffmpeg -i '#{infile_wsl}' -ab 192k '#{outfile_wsl}'")
end

これを 拡張子batで文字コードUTF-8)保存してやると、ドラッグアンドドロップされたファイルを引数として実行できるファイルが完成する(なお、RubyWindows側にインストールされていて、rubyコマンドプロンプトで入力すれば呼び出せる状態になっている必要がある)。

え? 「Rubyスクリプトじゃなくてバッチファイルじゃないか」って?

……これが今回紹介する手法である「Rubyスクリプトとしても実行できるバッチファイル」である。

要するに「バッチファイルからRubyスクリプトを起動する方法だと2ファイルになってしまうけど、じゃあRubyスクリプトを自分自身に書いてしまえば1ファイルで済むよね」と、そういう話だ。

別にこれは私が考えたものではなくて、「複数のプログラミング言語として解釈可能なプログラム」は polyglot という名で呼ばれている*1

en.wikipedia.org

「超絶技巧プログラミング」とか言われているような非実用的なお遊びプログラミングの方面の手法という印象が強かったのだけれど、こういう風に実用的に役立つ場面もあるのである。

さて、上に貼ったWikipediaにはPerlとしても実行できるバッチファイルとして

 @rem = ' --PERL--
 @echo off
 perl "%~dpnx0" %*
 goto endofperl
 @rem ';
 #!perl
 print "Hello, world!\n";
 __END__
 :endofperl

が紹介されているが、似たことをするにしても私の記したRubyの例は少し黒魔術感が減っていると思う。 これは ruby-x オプションという便利なもののおかげである。

-x[directory]

メッセージ中のスクリプトを取り出して実行します。スクリプトを読み込む時に、#!で始まり, "ruby"という文字列を含む行までを読み飛ばします。スクリプトの終りはEOF(ファイルの終り), ^D(コントロールD), ^Z(コントロールZ)または予約語__END__で指定されます。

ディレクトリ名を指定すると、スクリプト実行前に指定されたディレクトリに移動します。

これで前半のバッチファイル部分は読み飛ばしてくれるから、Wikipediaの例のように頑張って文字列リテラルに見せかけたりする必要はないのである。

Perlの場合

ruby-x オプションとほぼ同じ働きをする -x オプションがPerlにもある。というかRubyのオプションはおそらくPerl由来である(要出典)。

なお、ドキュメントには

-x
-xdirectory

メールのような大きな無関係のテキストのかたまりの中にプログラムが 埋め込まれている事を Perl に伝えます。 最初の #! で始まり、"perl" という文字列を含む行までの、先行するゴミは 捨てられます。 その行にある意味を持つスイッチは適用されます。

(後略)

とある。 polyglotは後から出てきた使い方で、この機能は本来メールでプログラムやパッチをやりとりしていた時代の遺物なのだろう。

面倒なのでmp3変換の例は書かないけれど、ほとんどRubyと同じ使い方ができるはずだ。

Pythonの場合

Pythonの場合も同様のことはできるのだが、事情は少しだけ異なる。

-x

Unix 以外の形式の #!cmd を使うために、ソースの最初の行をスキップします。これは、DOS専用のハックのみを目的としています。

とのことで、最初の1行しか読み飛ばしてくれない。

だが対応策は単純で、バッチファイル部分を1行に収めればよい。

chcp 65001 & cd /d %~dp0 & python -x %0 %* & exit /b 0
#!python
print("Pythonから話しかけています")
import time
time.sleep(5)

シェルスクリプトでの;に相当するコマンドの連結が、バッチファイルでは&でできるので、それを使って改行をなくしてやるだけである。

結語

以上、バッチファイルとして各種軽量プログラミング言語スクリプトを実行する方法を紹介した。古典的な方法であるが、バッチファイルで苦労する機会が減らせるかもしれない。

なお、これと同様のことはバッチファイルではなくLinuxシェルスクリプトでも可能だが、シェルスクリプトならヒアドキュメントを使ってもほぼ同じ事が実現可能である(そしてヒアドキュメントのほうが素直だと思う)。

*1:polyglot の本来の意味は「多国語に通じた人」「多国語で書かれた書物」等。