設定ファイルから読み込んだパラメータ内容をGetopt::Lucidを使ってコマンドラインオプションで上書きする
設定ファイルとコマンドラインオプションの優先順位の問題
スクリプトの中のパラメータの一部をコマンドライン引数で指定した設定ファイルから読み込むようにしたい、 さらにその内の任意の項目をコマンドラインオプション指定で上書きできるようにしたいと思うわけです。
例えば次のような設定ファイルconfig.yamlがあって、
--- db_name: TestDB db_user: TestUser db_pass: TestPass num_of_workers: 5
コマンドラインから次のようにスクリプトを起動すると、
$ ./myapp.pl --db_name="hogehoge" config.yaml
db_name だけは「hogehoge」で、その他のパラメータは config.yaml の内容で動作するようにしたい。
つまりコンフィグファイルの内容よりもコマンドラインオプションの指定が優先されるようにしたいのですが、 これには悩ましい点があって、たとえば 「業務に役立つPerl」 という書籍などではコマンドラインオプションの処理方法としてGetopt::Longを使った次のような方法が紹介されているのですが、
# 1) Set defaults my $db_name = "db_default_name"; # 2) read options GetOptions( 'db_name' => \$db_name, ); # 3) read arguments my $config_file = shift;
設定ファイルconfig.yamlの読み込み処理をこのあとに何も考えずに書くと# 2)の コマンドラインオプション内容を上書きしてしまいそうです。 そうなると意図した優先順位と逆転してしまう。
Getopt::Lucidを使う
Getopt::Longの替わりにGetopt::Lucidを使うとこの悩みをスマートに解決できます。
perldoc Getopt::Lucidで「Managing Defaults and Config Files」の節にこの優先順位の問題が論じられています。 前記のコードでの# 2)のコマンドラインオプションの取得において、 Getopt::Lucid # 1)のデフォルト値を内部的に保持し続け、# 3)のあとに 設定ファイルを読み込んだあとに # 2)ではなく# 1)のデフォルトを 置き換えて評価しなおすことが可能です。
少し長くなりますが例えばこんなようなコードになると思います。
use Getopt::Lucid qw( :all ); use Config::Any; # 1) my @specs = ( Param("db_name")->default("db_test"), Param("db_user")->default("user_test"), Param("db_pass")->default("pass_test"), Param("num_of_workers")->default(8), ); # 2) my $opt = Getopt::Lucid->getopt(\@specs); # 3) my $config_file = shift; my $cfg = Config::Any->load_files( { files => [$config_file], use_ext => 0, flatten_to_hash => 0, } ); my ($filename, $config) = %$cfg; # デフォルトをconfig.yamlの内容で置き換える $opt->replace_defaults($config); # Results print $opt->get_db_name."\n"; print $opt->get_db_user."\n"; print $opt->get_db_pass."\n"; print $opt->get_num_of_workers."\n";
これをコマンドラインオプションなしで実行すると
$ ./myapp.pl config.yaml TestDB TestUser TestPass 5
コマンドラインオプションを指定して実行すると
$ ./myapp.pl --db_name="hogehoge" --num_of_workers=12 config.yaml hogehoge TestUser TestPass 12
と、期待通りの結果となります。
その他のあれこれ
「Lucid」とは英語で「頭脳明晰な」「わかりやすい」といった意味の単語だそうです。 上記の他にもオプション値の正当性評価やエラー時の例外処理の仕組みなどが 組み込まれているようですので、おいおいに使い方を覚えようと思っています。