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

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

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

参照用 記事

シェルスクリプトの代わりに使う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= max lines to process
--file= 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 of the application
--version= version of the application
--overview use overview
--nooverview do not use overview
--overview-file= path to the overview file
--in= input directory
--out= 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 の省略でしょうが、ピーオプトよりポップティーと呼んだほうが楽しそうでいいと思うな。