このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

Windowsのcmd.exeとbashのどちらでも実行できるバッチファイルの書き方

僕はWindows上でMinGW/MSYSを使っています。シェルはbashを使っていることが多いのですが、Windowsのcmd.exeもけっこう使います。cmd.exe向けに書いたバッチファイル(コマンドファイル)を、bashから実行したいときがたまにあります。どうしたらいいのでしょう。

バッチファイルとbash

例題として、次のようなバッチファイルshow-memo.batを考えます。


@echo off
rem 一時的なメモ書きを表示
if not exist C:\tmp\MEMO.txt goto NoFile
type C:\tmp\MEMO.txt
goto End

:NoFile
echo メモ C:\tmp\MEMO.txt はありません.

:End

show-memo.batは、PATH環境変数に含まれるディレクトリに置いてあるとします。当然に、cmd.exeからshow-memo.batは実行できます。

さて、bashからはどうでしょう。


$ show-memo
bash: show-memo: command not found

$ show-memo.bat
/c/Users/hiyama/Work/bin/show-memo.bat: line 1: @echo: command not found
/c/Users/hiyama/Work/bin/show-memo.bat: line 2: rem: command not found
/c/Users/hiyama/Work/bin/show-memo.bat: line 13: syntax error: unexpected end of
file

$

拡張子.batを付ければ、ファイルを検索して実行してくれますが、cmd.exe用のバッチファイルですからうまく実行できません。

bashからcmd.exeを起動すればうまくいきます。


$ cmd.exe //c show-memo
メモ C:\tmp\MEMO.txt はありません.

$

そうであれば、cmd.exeの起動を短い別名にしておけばよさそうです。


$ alias c='cmd.exe //c'

$ c show-memo
メモ C:\tmp\MEMO.txt はありません.

$

これで十分に実用になるのですが、「show-memo.bat とだけ入力して実行したい」気がしてきました。特に理由はありませんけど。

bashからも実行できるバッチファイル

ActivePerlに含まれるバッチファイルでは、巧みな工夫で、バッチファイルをPerlスクリプトとしても実行できるようになっています。その先頭部分はこんな感じです。


@rem = '--*-Perl-*--
@echo off
if "%OS%" == "Windows_NT" goto WinNT
perl -x -S "%0" %1 %2 %3 %4 %5 %6 %7 %8 %9
goto endofperl
:WinNT
perl -x -S %0 %*
if NOT "%COMSPEC%" == "%SystemRoot%\system32\cmd.exe" goto endofperl
if %errorlevel% == 9009 echo You do not have Perl in your PATH.
if errorlevel 1 goto script_failed_so_exit_with_non_zero_val 2>nul
goto endofperl
@rem ';
#!perl
#line 15

use strict;
use warnings;

Perlにとっては特に意味のない代入文の文字列内にバッチスクリプトが書き込んであります。同じように、bashにとっては特に意味のないナニカにバッチスクリプトを書けばよさそうです。

このナニカがなかなか見つからなかったのですが、やっとのことでコロン(「:」)に思い至りました。

バッチファイルにおけるコロンは、本来はgotoラベルの記述ですが、コロンからはじまる行をコメント行のようにも使えます。(今回の目的では)幸いなことに、コロンの後に「<」のような特殊記号を書いても無視してくれます。一方、bashにとってのコロンは何もしないコマンドです。何しないだけで、通常のコマンドとして扱われます。

次のように書いてみます。


: <<EOF
ナニヤラ
カニヤラ
EOF

cmd.exeは1行目を無視して「ナニヤラ カニヤラ」の部分を解釈します。一方bashは、「ナニヤラ カニヤラ」の部分をヒアドキュメントと解釈しますが、コロンコマンドが何もしないので事実上EOFまでが無視されます。

この事実を利用して次のように書きます。太字の部分が細工した部分です。


: <<EOF
@echo off
goto Windows
EOF
exec cmd //c $0 $*
:Windows

rem 一時的なメモ書きを表示
if not exist C:\tmp\MEMO.txt goto NoFile
type C:\tmp\MEMO.txt
goto End

:NoFile
echo メモ C:\tmp\MEMO.txt はありません.

:End

既に知られたTipかも知れませんが、思いついたときは小躍りしました。