檜山正幸のキマイラ飼育記 このページをアンテナに追加 RSSフィード Twitter

キマイラ・サイトは http://www.chimaira.org/です。
トラックバック/コメントは日付を気にせずにどうぞ。
連絡は hiyama{at}chimaira{dot}org へ。
蒸し返し歓迎!
このブログの更新は、Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama
ところで、アーカイブってけっこう便利ですよ。

2008-10-02 (木)

シェルスクリプトの代わりに使うPerl 実例付き

| 13:30 | シェルスクリプトの代わりに使うPerl 実例付きを含むブックマーク

「OSが何でも、Perlスクリプトをコマンドとして気持ちよく起動する方法」において、Perlスクリプトなら、異なる環境でも同じように起動・実行できることを示しました。

もともとは、bashシェルスクリプトをWindowsでも使いたかったのです。MSYSやCygwinを使えばシェルスクリプトの実行はできますが、cmd.exeやGUIシェルからの実行は困難です。そこで、bashの代わりにPerlを使うことにしたわけです。

Perlを使うとはいっても、シェルスクリプトの代替なので、exec($cmd) や system($cmd) により他のコマンドを実行するのが主な目的です。以下では、サンプルとして、「Erlang実験室:コマンドラインから使うerl」で説明したような、バッチ的にerlコマンドを起動する例を取り上げます。より具体的には、erlの-evalオプションを使って、EDocを実行します。

内容:

  1. コマンドラインオプションの解析
  2. ヘルプメッセージの表示
  3. 実例

●コマンドラインオプションの解析

erlのコマンドライン引数も、EDocの起動関数も、けっこう面倒な構文です。Perlで皮をかぶせることによって、コマンドライン・インターフェースを改善しましょう。幸いにもPerlには、Getopt::Longというコマンドライン・オプション解析モジュールがあるので、これを利用します。使い方は次のような感じです。

use Getopt::Long;

# コマンドラインオプションの値を受け取る変数
# とデフォルト値を宣言する。
my $opt_verbose = 0;
my $opt_max = 100;
my $opt_file = "input.txt";

# GetOptionsによりコマンドライン・オプションを解析し、
# 変数に値を設定する。
GetOptions(
    'verbose' => \$opt_verbose, 
    'max=i' => \$opt_max,
    'file=s' => \$opt_file);

# 試しに変数を表示してみる。
print "\$opt_verbose = $opt_verbose\n";
print "\$opt_max = $opt_max\n";
print "\$opt_file = \"$opt_file\"\n";

Getopt::Longに関してより詳しくは、次の文書などを参照してください。

●ヘルプメッセージの表示

例えば、コマンドライン・オプション解析のCライブラリであるpopt*1では、ヘルプメッセージの表示までサポートしています。

  • void poptPrintHelp(poptContext con, FILE * f, int flags);
  • void poptPrintUsage(poptContext con, FILE * f, int flags);

Getopt::Long はヘルプメッセージの面倒は見てくれないので、Pod::Usageにあるpod2usageという関数(サブルーチン)を使うのが通例のようです。pod2usageのデフォルトでは、スクリプトソース内に埋め込まれたPOD(文書)をヘルプメッセージとして表示します。ソースを次のような形にしておけばいいでしょう。

use Getopt::Long;
use Pod::Usage;

# コマンドラインオプションの値を受け取る変数
# とデフォルト値を宣言する。
my $opt_verbose = 0;
my $opt_max = 100;
my $opt_file = "input.txt";
my $opt_help = 0;

# GetOptionsによりコマンドライン・オプションを解析し、
# 変数に値を設定する。
my $opt_ok = GetOptions(
    'verbose' => \$opt_verbose, 
    'max=i' => \$opt_max,
    'file=s' => \$opt_file,
    'help|?' => \$opt_help,
    );
# 必要なら使用法を表示(すぐに終了)
pod2usage(-verbose => 2) if $opt_help or !$opt_ok;

# 試しに変数を表示してみる。
print "\$opt_verbose = $opt_verbose\n";
print "\$opt_max = $opt_max\n";
print "\$opt_file = \"$opt_file\"\n";

#
# ここに処理を書く。
# 
__END__

=head1 Usage: mycmd [options]

=head2 Options:
  --verbose      display messages
  --max=<number> max lines to process
  --file=<path>  file to process
  --help  -?     show this help
=cut

__END__の後に書かれているPODは、manページ風の書き方からはズレていますが、コマンドのヘルプメッセージとしては適切な表示になります。([追記]←この点に関しては後の追記参照[/追記]

●実例

EDocをバッチ的に呼び出して処理をするコマンドです。すぐ上の例のように、__END__の後にPODを書く例がよく紹介されているのですが、これだとオプション処理のコードとヘルプメッセージが遠く離れて見にくいので、僕は、オプション処理コードのすぐ近くにPODを挿入しています。引用符のエスケープはけっこう煩雑です。エスケープ祭りを見て慣れておくといいかもしれません :-)

[追記]

あらためて眺めて見ると、埋め込まれたPODの部分をヒアドキュメントにしても変わらないような、、、 これじゃあ、「なんのためのpod2usageか?」と疑問を感じちゃいますね。やっぱり、そのままmanページに使えるようなドキュメントを書いておいて、それからusageを生成するのが期待されている使い方なんでしょう。-verboseの値を変更して、構文だけのヘルプから、フルマニュアルまで表示し分けるなんてのも出来ますしね。

という次第で、以下のようなケースでは、pod2usageの恩恵はあまりないということです。失礼しました。

[/追記]

#!/usr/bin/perl

use strict;
use warnings;
use Getopt::Long;
use Pod::Usage;

# コマンドラインオプションの値を受け取る変数
my $simple = 0;
my $app_name = "noname";
my $version = "0.0";
my $overview = 0;
my $overview_file = ""; # 後で調整する
my $src_dir = ".";
my $doc_dir = ""; # 後で調整する
my $private = 1;
my $norc = 0;
my $help = 0;

# コマンドラインオプションの取得と設定
my $opt_ok = GetOptions(
    'simple' => \$simple,
    'name=s' => \$app_name,
    'version=s' => \$version,
    'overview!' => \$overview,
    'overview-file=s' => \$overview_file,
    'in=s' => \$src_dir,
    'out=s' => \$doc_dir,
    'private!' => \$private,
    'norc' => \$norc,
    'help|?' => \$help
    );
=pod

=head1 Usage: edoc [options]

=head2 Options:
  --simple        simple directory layout
  --name=<name>   name of the application
  --version=<ver> version of the application
  --overview      use overview
  --nooverview    do not use overview
  --overview-file=<path>  path to the overview file
  --in=<path>     input directory
  --out=<path>    output directory
  --private       document private functions
  --noprivate     do not document private functions
  --norc          use start_norc boot file
  --help  -?      show this help

=cut

# 必要なら使用法の表示(すぐに終了)
pod2usage(-verbose => 2) if $help or !$opt_ok;

# オプションの依存関係を調整
if ($simple) {
    $doc_dir = "$src_dir/doc" unless $doc_dir;
} else {
    $doc_dir = "$src_dir/../doc" unless $doc_dir;
}
$overview = 1 if $overview_file;
if ($overview) {
    $overview_file = "$src_dir/overview.edoc" unless $overview_file;
}
my $boot = ($norc? "start_norc" : "start_clean");

# EDocのオプションを組み立てる
my $edoc_opts = 
    "{def,{version, \"$version\"}}, " .
    "{def,{app_name, \"$app_name\"}}, " .
    ($overview? "{overview, \"$overview_file\"}, " : "") . 
    ($private ? "private, " : "") .
    "{dir, \"$doc_dir\"}";

# オプション指定データをコマンドライン用にエスケープする
# (Windows cmd.exe では「'」が使用できないので)
my $escaped_edoc_opts_list = "[" . $edoc_opts . "]";
$escaped_edoc_opts_list =~ s@\\@\\\\@g; # \ --> \\
$escaped_edoc_opts_list =~ s@\"@\\\"@g; # " --> \"

# コマンドラインを組み立てる
my $edoc_cmd = "erl " .
    "-boot $boot -noshell " .
    "-eval \"edoc:application('$app_name', \\\"$src_dir\\\", " .
    "$escaped_edoc_opts_list)\" " .
    "-s init stop";

print "OPTS=$edoc_opts\n"; # デバッグ用、後で削除
print "CMD =$edoc_cmd\n"; # デバッグ用、後で削除

# コマンド実行
system($edoc_cmd)

*1:poptは、おそらく parse options の省略でしょうが、ピーオプトよりポップティーと呼んだほうが楽しそうでいいと思うな。