週末には少しPerlを。

Perlスクリプトの学習日記です。

設定ファイルから読み込んだパラメータ内容を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」とは英語で「頭脳明晰な」「わかりやすい」といった意味の単語だそうです。 上記の他にもオプション値の正当性評価やエラー時の例外処理の仕組みなどが 組み込まれているようですので、おいおいに使い方を覚えようと思っています。