sironekotoroの日記

Perl で楽をしたい

Windows のバッチファイルの中に書かれた Perl スクリプトを実行する

さて、Windows 環境で Perl と仲良くする!仲良くなりたい!って昨今です。

まぁ、仲良くする目的は仕事を楽にして twitter 眺める時間を捻出するとか、早く寝るとか、そういう真の目的のためですが・・・

前回は「バッチファイルから Perl を呼び出す」ってのやりました。

sironekotoro.hateblo.jp

そういえば

Perl 入学式でおなじみの Web フレームワークである Mojolicious::Lite ですが、Windows 環境にインストールして利用することも可能です。

もちろん、試験用のサーバ morboWindows で動きます。

そして、この morbo はバッチファイル morbo.bat なのです!

where コマンドで morbo の場所を探すとこんな感じです。

C:\Users\sironekotoro>where morbo
C:\Strawberry\perl\bin\morbo
C:\Strawberry\perl\bin\morbo.bat

ほんで、この C:\Strawberry\perl\bin\morbo.bat をメモ帳で開いてみると・・・最初の十数行こそバッチファイルですが、途中から Perl がそのまま書かれてますね!

コードはこちら、クリックで展開

@rem = '--*-Perl-*--
@set "ErrorLevel="
@if "%OS%" == "Windows_NT" @goto WinNT
@perl -x -S "%0" %1 %2 %3 %4 %5 %6 %7 %8 %9
@set ErrorLevel=%ErrorLevel%
@goto endofperl
:WinNT
@perl -x -S %0 %*
@set ErrorLevel=%ErrorLevel%
@if NOT "%COMSPEC%" == "%SystemRoot%\system32\cmd.exe" @goto endofperl
@if %ErrorLevel% == 9009 @echo You do not have Perl in your PATH.
@goto endofperl
@rem ';
#!perl
#line 16
use Mojo::Base -strict;

use Mojo::Server::Morbo;
use Mojo::Util qw(extract_usage getopt);

getopt
  'b|backend=s' => \$ENV{MOJO_MORBO_BACKEND},
  'h|help'      => \my $help,
  'l|listen=s'  => \my @listen,
  'm|mode=s'    => \$ENV{MOJO_MODE},
  'v|verbose'   => \$ENV{MORBO_VERBOSE},
  'w|watch=s'   => \my @watch;

die extract_usage if $help || !(my $app = shift);
my $morbo = Mojo::Server::Morbo->new;
$morbo->daemon->listen(\@listen) if @listen;
$morbo->backend->watch(\@watch)  if @watch;
$morbo->run($app);

=encoding utf8

=head1 NAME

morbo - Morbo HTTP and WebSocket development server

=head1 SYNOPSIS

  Usage: morbo [OPTIONS] [APPLICATION]

    morbo ./script/my_app
    morbo ./myapp.pl
    morbo -m production -l https://*:443 -l http://[::]:3000 ./myapp.pl
    morbo -l 'https://*:443?cert=./server.crt&key=./server.key' ./myapp.pl
    morbo -w /usr/local/lib -w public -w myapp.conf ./myapp.pl

  Options:
    -b, --backend <name>           Morbo backend to use for reloading, defaults
                                   to "Poll"
    -h, --help                     Show this message
    -l, --listen <location>        One or more locations you want to listen on,
                                   defaults to the value of MOJO_LISTEN or
                                   "http://*:3000"
    -m, --mode <name>              Operating mode for your application,
                                   defaults to the value of
                                   MOJO_MODE/PLACK_ENV or "development"
    -v, --verbose                  Print details about what files changed to
                                   STDOUT
    -w, --watch <directory/file>   One or more directories and files to watch
                                   for changes, defaults to the application
                                   script as well as the "lib" and "templates"
                                   directories in the current working
                                   directory

=head1 DESCRIPTION

Start L<Mojolicious> and L<Mojolicious::Lite> applications with the L<Mojo::Server::Morbo> web server.

=head1 SEE ALSO

L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.

=cut
__END__
:endofperl
@set "ErrorLevel=" & @goto _undefined_label_ 2>NUL || @"%COMSPEC%" /d/c @exit %ErrorLevel%

ほほう!興味深い!!そしてわけわからん!!!

いや、うっすらはわかるけど、気になる、ってことでやっていきますよ。

何をやっていくって? 1 行ごとにコメントつけるんだよ!

頭の良い人はスラスラ推測つけて(しかもその推測あたってる)読み飛ばしていけるんだろうけど、うちはこう、アレがナニでダメな人なので・・・オランダ語医学書を解読した江戸期の人々みたいにやっていきます。

やっていく

@rem = '--*-Perl-*--

1 行目はバッチファイルのコメント行ですね。

ちなみに、先頭に @ つけるとコマンドプロンプトで結果が非表示になります。すっきり。でもデバッグの時は外すとよいです。

@set "ErrorLevel="

続いて、環境変数を宣言。= イコール 込みの変数名?ってなんか変ですね

@if "%OS%" == "Windows_NT" @goto WinNT

実行環境の OS の種類を判別して、"%OS%" == "Windows_NT" であれば goto でラベル WinNT に飛ばしています。

なお、Windows10 で実行すると Windows_NT なります。なので、Windows10 環境だとここからラベル WinNT まで飛びます

C:\Users\sironekotoro>echo "%OS%"
"Windows_NT"

うちが動かしているのは Windows10 上なので、以下の 3 行は関係ないのですが念のため・・・

@perl -x -S "%0" %1 %2 %3 %4 %5 %6 %7 %8 %9

Perl-x-Sコマンドラインオプションをつけて、バッチファイルの引数のプレースホルダ 10 個つけて起動してます。

では -x とは?ここは公式の日本語化をやってる perldoc.jp さんから

perldoc.jp

-x directory メールのような大きな無関係のASCII テキストのかたまりの中に プログラムが埋め込まれている事を Perl につたえます。 最初の #! で始まり、"perl" という文字列を含む行までの、 先行するゴミは捨てられます。 その行にある意味を持つスイッチは適用されます。 directory が指定されると、Perl はプログラムの実行前に、 そのディレクトリに移ります。 -x スイッチは先行するゴミの処分を制御するだけです。 プログラムの後に無視すべきゴミがある場合には、 END でプログラムを終了する必要があります (その、後に続く ゴミの一部または全部は、必要に応じて DATA ファイルハンドルを通して、 そのプログラムで処理する事ができます)。

へー! Perl って他のテキストファイルとかに埋め込むことできるんですね!知らんかった。というか、この morbo のバッチが確かにそうだわ。なるほどう!

つまり、ここで #!perl から __END__ までの Perl スクリプトが動くと。

バッチファイルに書いてある Perl が動く仕組みはこれだったのね。

つぎに -S とは?

Perl がプログラムを探すときに環境変数 PATH を参照するようにします (プログラム名がディレクトリセパレータを含むときを除きます)。

このあともすごい長い、というか濃い解説が続きますが、Windows の PATH を読んでくれる、ってことね(ざっくり理解)。

@set ErrorLevel=%ErrorLevel%

%ErrorLevel% は直前のコマンドの実行結果がどうであったか、コマンドの終了コードを取得するもの。それを環境変数 ErrorLevel=なし)に代入している。これは 2 行目で宣言した ErrorLevel= とは異なる変数。

@goto endofperl

そして、endofperl のラベルがあるところ(末尾から2行目)まで飛ぶ、と。

まだまだやっていく

:WinNT

WinNT と判定された 3 行目からこのラベルに移動してくる。

@perl -x -S %0 %*

-x-S は既に調べた。で、引数のところが %0 %* と短くなってる。既に書いたけど、バッチファイル中の Perl をここで実行する。

@set ErrorLevel=%ErrorLevel%

ここも前述なので飛ばす。と言いつつ書くと、上の perl -x -S ... の終了コードを代入している。

morbo は ctrl - c で止めるけど、その終了コードをここで拾っているってことなのかな?

@if NOT "%COMSPEC%" == "%SystemRoot%\system32\cmd.exe" @goto endofperl

この %COMSPEC%コマンドプロンプト の本体、 cmd.exe の本体パスとのこと。

if NOT なので、標準のパス以外だったら終了のラベル endofperl に飛ばしてる。

@if %ErrorLevel% == 9009 @echo You do not have Perl in your PATH.

Perl を実行した結果の終了コードが 9009 の場合には Perl が入っていないと判断する、ってのはわかる。

ググってみたりすると、 9009 ってのはファイルが見つからない時のエラーコードらしい。ほうほう。

@goto endofperl

エラーが出たら、endobperl ラベルへ〜

@rem ';

コメントで ; のみだけど、これは区切りって意味かな?

で、ここからは Perl 。引数を受け取って、morbo を起動してますね。ほんで =encoding utf8 からは POD で解説と。

#!perl
#line 16
use Mojo::Base -strict;

use Mojo::Server::Morbo;
use Mojo::Util qw(extract_usage getopt);

getopt
  'b|backend=s' => \$ENV{MOJO_MORBO_BACKEND},
  'h|help'      => \my $help,
  'l|listen=s'  => \my @listen,
  'm|mode=s'    => \$ENV{MOJO_MODE},
  'v|verbose'   => \$ENV{MORBO_VERBOSE},
  'w|watch=s'   => \my @watch;

die extract_usage if $help || !(my $app = shift);
my $morbo = Mojo::Server::Morbo->new;
$morbo->daemon->listen(\@listen) if @listen;
$morbo->backend->watch(\@watch)  if @watch;
$morbo->run($app);

=encoding utf8

=head1 NAME

morbo - Morbo HTTP and WebSocket development server

=head1 SYNOPSIS

  Usage: morbo [OPTIONS] [APPLICATION]

    morbo ./script/my_app
    morbo ./myapp.pl
    morbo -m production -l https://*:443 -l http://[::]:3000 ./myapp.pl
    morbo -l 'https://*:443?cert=./server.crt&key=./server.key' ./myapp.pl
    morbo -w /usr/local/lib -w public -w myapp.conf ./myapp.pl

  Options:
    -b, --backend <name>           Morbo backend to use for reloading, defaults
                                   to "Poll"
    -h, --help                     Show this message
    -l, --listen <location>        One or more locations you want to listen on,
                                   defaults to the value of MOJO_LISTEN or
                                   "http://*:3000"
    -m, --mode <name>              Operating mode for your application,
                                   defaults to the value of
                                   MOJO_MODE/PLACK_ENV or "development"
    -v, --verbose                  Print details about what files changed to
                                   STDOUT
    -w, --watch <directory/file>   One or more directories and files to watch
                                   for changes, defaults to the application
                                   script as well as the "lib" and "templates"
                                   directories in the current working
                                   directory

=head1 DESCRIPTION

Start L<Mojolicious> and L<Mojolicious::Lite> applications with the L<Mojo::Server::Morbo> web server.

=head1 SEE ALSO

L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.

=cut
__END__
:endofperl

で、ここが終着点のラベル endofperl と。最終的にはこのラベルにくる、はず?

@set "ErrorLevel=" & @goto _undefined_label_ 2>NUL || @"%COMSPEC%" /d/c @exit %ErrorLevel%

さーて一気にわからなくなった。

ええっと、バッチファイルでの & 、これは commandA & commandB と書いた場合に、commandA を実行したあとに commandB を実行する、というイディオムらしい。

ふむふむ。

この & の右側は _undefined_label_ これはわからんけど、定義してないラベルに @goto しろってことかな。

で、 || はさっきの & とは異なって、 commandC || commandD となり、 commandC が 終了結果が真の場合には commandD は実行しない、と。

@"%COMSPEC%" /d/c @exit %ErrorLevel%

で、commandC が偽だった場合にはこれが実行される。

%COMSPEC%コマンドプロンプト のパスなので、そこに /d/c オプションをつけて実行し、@exit すると。

つまり、 commandX & commandY || commandZ ってことかな?ええと、これは左?右?どっちから評価されるんだ?

・・・とりあえず、ながかったー!

バッチファイル の中で Perl をうごかす!

長く書いてきたのは全て自分のため・・・ですね!

ってことで、morbo.bat 参考にして作ったバッチファイルがこちらです。

@perl -x -S %0 %*
@set ErrorLevel=%ErrorLevel%
@if NOT "%COMSPEC%" == "%SystemRoot%\system32\cmd.exe" @goto endofperl
@if %ErrorLevel% == 9009 @echo You do not have Perl in your PATH.
@goto endofperl
@rem この下からPerl ;
#!perl
use strict;
use warnings;

my $str = "hello bat file!";
print "$str\n";

print "@ARGV";  # コマンドライン引数があれば表示される

# Perl はこの下の __END__ まで
__END__
:endofperl
@set "ErrorLevel=" & @goto _undefined_label_ 2>NUL || @"%COMSPEC%" /d/c @exit %ErrorLevel%

参考にしたページ

Perl については文中にページへのリンク書いてきましたが、コマンドプロンプト に関してはこちらのページを多く参考にさせてもらいました。ありがとうございました。

www.pg-fl.jp