Perlでコマンドラインオプションを解析する場合、大体は標準添付されているGetopt::Longを使うと思いますが、自分は3年前ぐらいから Getopt::Compact というモジュールに出会い、それ以降大抵の場合はこれを使っています。このモジュールの良いところは
- オプションの定義(-c, –config などの定義)とそのヘルプメッセージが一箇所で定義できる
- -h, –helpオプションを最初から定義してくれてる(–manオプションも)
- 定義してないオプションが指定されると自動的にエラーにしてくれる
- Getopt::Compact#usage でヘルプメッセージが簡単に取得できる
があると思っています。などと抽象的な話をしてもしょうがないので、具体的なコードを書いてみます。
#!/usr/bin/env perl
use strict;
use warnings;
use Getopt::Compact;
use Data::Dumper;
my $getopt = Getopt::Compact->new(
name => 'getopt_compact.pl',
modes => [ qw(verbose) ],
args => 'FILE',
version => '0.1',
struct => [
[ [ qw(f force) ], 'force overwrite of output file and compress links' ],
[ [ qw(l level) ], 'compress level', '=i' ],
[ [ qw(S suffix) ], 'use suffix on compressed files', '=s' ],
]
);
my $options = $getopt->opts;
my $file = shift @ARGV;
unless ($file) {
print STDERR $getopt->usage;
exit 1;
}
print "options -----\n";
print Dumper $options;
上の例では gzip のオプションのモノマネで
- -f, –forceというフラグオプション
- -l, –levelという数値のオプション
- -S, –suffixという文字列のオプション
- オプション以外に引数FILEを受け取る
という仕様としています。
でで、例えば
$ perl getopt_compact.pl --help
と実行すると
getopt_compact.pl v0.1
usage: getopt_compact.pl [options] FILE
options
-h, --help This help message
-v, --verbose Verbose mode
-f, --force Force overwrite of output file and compress links
-l, --level Compress level
-S, --suffix Use suffix on compressed files
--man Display documentation
のようにヘルプが表示されます。また、–hoge のような存在しないオプションを指定すると
$ perl getopt_compact.pl --hoge
Unknown option: hoge
getopt_compact.pl v0.1
usage: getopt_compact.pl [options] FILE
options
-h, --help This help message
-v, --verbose Verbose mode
-f, --force Force overwrite of output file and compress links
-l, --level Compress level
-S, --suffix Use suffix on compressed files
--man Display documentation
のようにエラーになりヘルプが表示されます。
という感じで、Getopt::Longより依存モジュールがあるものの便利に使わせてもらってます。ただ、PerlのGetopt系のモジュールは他にも色々あるのでもっと便利なのがありそうですが、こういうのもあるよという紹介でした。
だいぶ前の話、Perlを使わないでShellスクリプトで頑張っていた頃、Shellでコマンドラインオプションの解析をやる時は
#!/bin/sh
for OPT in $*
do
case $OPT in
'-x' )
FLAG_X="TRUE"
;;
'-y' )
shift
FLAG_Y="TRUE"
VALUE_Y=$1
;;
esac
shift
done
if [ "$FLAG_X" = "TRUE" ]; then
echo "Option -x specified."
fi
if [ "$FLAG_Y" = "TRUE" ]; then
echo "Option -y $VALUE_Y specified."
fi
という感じで $* と shift を使ってやっていたのですが、最近 getopts なる素敵なビルトインコマンドを知りました。これを使うと上のスクリプトは下のように書き直すことができます。
#!/bin/sh
while getopts xy: OPT
do
case $OPT in
"x" )
FLAG_X="TRUE"
;;
"y" )
FLAG_Y="TRUE"
VALUE_Y=$OPTARG
;;
esac
done
if [ "$FLAG_X" = "TRUE" ]; then
echo "Option -x specified."
fi
if [ "$FLAG_Y" = "TRUE" ]; then
echo "Option -y $VALUE_Y specified."
fi
スクリプトの行数自体はあんまり変わらないように見えますが、shift 忘れをよくやらかしていたので、それが無くなった分つまらないバグを入れ込まなくなった気がします。
getoptsの解説
getopts の引数にオプションの文字を指定しますが、文字のあとに : (コロン)をつけると、引数ありのオプションという扱いになります。さらに
のように未定義のオプションを指定すると
/home/oinume/tmp/script/getopts.sh: illegal option -- z
というように怒られます。このエラーハンドリングを自前でやるには :xy: のように、getopts の引数の文字列の最初を : にすればいいみたい。
さらに $OPTIND という変数を下記のように -1 してやることで、オプション以降に与えられた引数を取得することができます。例えば、上のスクリプトに下記を足して
shift `expr $OPTIND - 1`
if [ -n "$1" ]; then
echo "Argument $1 specified."
fi
$ ~/tmp/script/getopts.sh -x -y yyy hoge
のように実行すると
Option -x specified.
Option -y yyy specified.
Argument hoge specified.
となります。getopts を実行すると、$OPTIND にはオプションのインデックス番号が保存されているので、これを -1 して shift してやることで、オプション以降の引数が取得できるようになるという仕組みです。
最近はこういうレベルのプログラムだったらPerlで書くことも多かったのですが、Shell でも十分いけそうだと実感しました。

詳解 シェルスクリプト
著者/訳者:Arnold Robbins Nelson H. F. Beebe
出版社:オライリージャパン( 2006-01-16 )
大型本 ( 345 ページ )
Shellスクリプトで配列のマージってどうやるんだろうって思ったので調べてみたら、${ARRAY[*]} か ${ARRAY[@]} で配列の要素を全部取得できるらしいので、それを () 使ってマージすればいいらしい。
#!/bin/sh
ARRAY1=(1.1.1.1 2.2.2.2)
ARRAY2=(3.3.3.3 4.4.4.4)
ARRAY3=(4.4.4.4 5.5.5.5)
MERGED=(${ARRAY1[*]} ${ARRAY2[*]} ${ARRAY3[*]})
for n in ${MERGED[*]}; do
echo $n
done
を実行すると
1.1.1.1
2.2.2.2
3.3.3.3
4.4.4.4
4.4.4.4
5.5.5.5
となる。重複してる値はもちろんケアしてくれないので、頑張って取り除く必要あり。
表題の通りなのですが、やっぱり自宅サーバでサービスを運営するのがすごい嫌になったのでLinodeを契約しました。プランは一番安いLinode 360で、契約してもうかれこれ3週間ぐらいですが、かなり快適に使ってます。良い点を挙げると…
- データセンターにFremont(カリフォルニア)を選ぶと、SSHでストレスはない(Slicehostの時はアメリカの真ん中でネットワーク遅延が激しかったので使い物にならなかった)
- CPUが(空いてれば)最大で4つまで使える。ちなみにCPUは”Intel(R) Xeon(R) CPU L5520 @ 2.27GHz” でかなり速い
- まだ試してないけど簡単にプランがアップグレードできる(12分間のダウンタイムあり)
- OSが自由に選べる(日本のVPSだと選択肢が少なかったり)
という感じで、今のところ不満はありません。なお契約してから知ったのですが、メモリの増量も 90MBで$5 / month で可能なようです。というわけでもしVPS探している人がいればこちらからどうぞ。(僕にアフィリエイトが入ります)
先日設定したサーバでMuninをインストールして監視していたのですが、どうもMySQL関連のプラグイン(mysql_bytes, mysql_queries, mysql_threadsなど)でグラフが描画されず、どうしたものかと思ってぐぐってみたところ、このエントリを見つけました。まさにビンゴで /etc/munin/plugin-conf.d/munin-node に以下の行を足してmunin-nodeをrestartしたところ、うまくグラフが描画されるようになりました。
env.mysqladmin /usr/local/bin/mysqladmin
Muninの問題なのか何なのか、自分のケースでは mysql 関連のコマンドを /usr/local/bin/ 配下にインストールしてたので、ここにPATHが通っていなかったのが問題のようです。
$ sudo -umunin munin-run mysql_bytes
と実行してもうまく数値が出てきたのでどうしたものかと悩んでいたのですが、解決してすっきりしました。
昔は tzconfig というのを使えばよかったらしいのですが、deprecated と言われました…
# dpkg-reconfigure tzdata
と入力して、Asia -> Tokyo を選択すればOKです。終わったら date コマンドで確認すればOK。
DBIのちょっとしたTIPSです。DBIには connect する時に
DBI->connect('dbi:mysql:database=hoge', 'root', 'whatever', { ... });
とオプションを渡すことができます。(たとえば RaiseError) 。このオプションのひとつに HandleError というものがあってデバッグにはなかなか便利なので紹介してみます。端的にいうと HandleError でサブルーチンを登録しておくと、エラーが発生したときにこのサブルーチンを使ってエラーを投げてくれるようになります。以下は具体例。
# MyDB.pm
package MyDB;
use strict;
use warnings;
use Carp ();
use DBI;
sub new {
bless {}, shift;
}
sub connect {
my ($self) = @_;
$self->{dbh} = DBI->connect(
'dbi:mysql:database=dbix_thin_test', 'root', 'root',
{ RaiseError => 1, HandleError => \&Carp::confess } # here
);
}
sub select {
my ($self, $sql) = @_;
my $sth = $self->{dbh}->prepare($sql); # error!
$sth->execute();
}
1;
# dbi_handle_error.pl
#!/usr/bin/env perl
use strict;
use warnings;
use FindBin qw($Bin);
use lib "$Bin";
use MyDB;
my $db = MyDB->new;
$db->connect();
$db->select("select * from not_exist");
と書いてわざと MyDB#select でエラーになるようにしておき、このスクリプトを実行します。
すると
$ perl ~/script/perl/dbi_handle_error.pl
DBD::mysql::st execute failed: Table 'dbix_thin_test.not_exist' doesn't existDBI::st=HASH(0x248a900) at /home/kazuhiro/script/perl/MyDB.pm line 23
MyDB::select('MyDB=HASH(0x21b8df0)') called at /home/kazuhiro/script/perl/dbi_handle_error.pl line 11
のように DBI->connectの HandleError で渡した Carp::confess が呼ばれ、エラーになった時にスタックトレースが表示されます。HandleError を指定しない場合は
$ perl ~/script/perl/dbi_handle_error.pl
DBD::mysql::st execute failed: Table 'dbix_thin_test.not_exist' doesn't exist at /home/kazuhiro/script/perl/MyDB.pm line 23.
DBD::mysql::st execute failed: Table 'dbix_thin_test.not_exist' doesn't exist at /home/kazuhiro/script/perl/MyDB.pm line 23.
のようになってしまい、実際にエラーが発生している箇所と問題のクエリの関連性がわかりにくいですが、HandleErrorで Carp::confess を指定しておくとファイル名と行番号がわかるのでどこでエラーになっているかあたりがつけやすいと思います。(この例だとメソッドのネストが少ないのでそのありがたみがわからないですが…)
以上、生DBIでもこんなマニアックなオプションがあるんだよという紹介でした。ZIGOROuさんの生 DBI ユーザーのための DBI Cookbook (1)を見ててよく思うのですが、DBIってなかなか奥が深いです。
コメント