【问题标题】:Separating configuration data and script logic in Perl scripts在 Perl 脚本中分离配置数据和脚本逻辑
【发布时间】:2011-01-23 19:14:59
【问题描述】:

我发现在我的 Perl 脚本中重复了以下反模式:脚本包含一些机器/设置特定设置,我将这些设置作为常量内联存储在脚本中,而脚本的其余部分本质上是通用的:

#!/usr/bin/perl

use strict;
use warnings;

# machine specific settings at the start of the script.
my $SETTING_1 = "foo";
my @SETTING_2 = ("123", "456");
my $SETTING_3 = "something";

# general part of script follows.
...

这种模式在一台机器上运行时还可以,但是一旦我想将脚本分发到多台机器上,麻烦就开始了,因为我必须跟踪,这样我通常不会用新的更新覆盖设置部分部分。

显然,正确的解决方案是拥有一个通用脚本文件并让它读取一个特定于脚本运行环境的配置文件。

我的问题是:你会推荐什么 CPAN 模块来解决这个问题?为什么?

【问题讨论】:

    标签: perl configuration configuration-files cpan


    【解决方案1】:

    通常的低技术方法是简单地do EXPR 一个配置文件。你调查过这个吗?

    【讨论】:

    • 这不提供任何错误检查,并允许执行任意代码。永远不要评估表达式或文件的内容,因为它们的内容可以简单地读入变量。
    • @Ether do 方法是内置的、简单且常用的。许多大型或流行的软件包仍然使用它来支持更安全但更复杂的配置方案,例如gna.org/projects/savanepackages.debian.org/sbuild 和 Net::Config(包含在 Perl 发行版中)。只要您信任创建配置文件的用户就可以了。
    【解决方案2】:

    对于配置数据,我更喜欢 YAMLYAML::XS。它简单、易读,并且具有几乎所有编程语言的绑定。另一个受欢迎的选择是Config::General

    【讨论】:

      【解决方案3】:

      对于配置文件,我喜欢使用YAML。简单、跨平台、人类可读,并且不会有您的配置意外变成实际程序的危险。

      【讨论】:

      • 我更喜欢 YAML::Tiny,因为它是轻量级的纯 Perl(如果需要,可以很容易地捆绑)。
      • @mobrule:我称之为人工可编辑。更改现有值是微不足道的。从头开始手动创建 YAML 文件有点困难。
      • 回复,yaml:查看yaml.org主页面的布局其实是yaml的。这有点整洁。 :)
      • 如果您的配置数据是结构化的(参考点:Catalyst),YAML 的人工编辑会变得非常糟糕。在 POD 文档中提供 YAML 示例也很痛苦,因为 POD 表示带有缩进的逐字块,而 perldoc 将它们呈现为缩进——而且 YAML 对缩进很敏感。
      【解决方案4】:

      Config:Properties 库非常适合读取和写入键/值对属性文件。

      【讨论】:

        【解决方案5】:

        我最喜欢的是Config::Std。我喜欢它处理multi-linemulti-part 配置值的方式。

        当一个变量可能多值时你必须小心:如果配置文件中存在单个值,它将将该值存储在一个标量中;如果存在多个值,您将获得一个数组引用。

        我发现有两个配置文件很方便:一个用于描述操作环境的值(在哪里可以找到库等),另一个用于用户可修改的行为。

        我也喜欢围绕它写一个包装器。例如(更新为包括自动生成的只读访问器):

        #!/usr/bin/perl
        
        package My::Config;
        use strict; use warnings;
        
        use Config::Std;
        use FindBin qw($Bin);
        use File::Spec::Functions qw( catfile );
        
        sub new {
            my $class = shift;
            my ($config_file) = @_;
        
            $config_file = catfile($Bin, 'config.ini');
            read_config $config_file => my %config;
        
            my $object = bless \%config => $class;
        
            $object->gen_accessors(
                single => {
                    install => [ qw( root ) ],
                },
                multi => {
                    template => [ qw( dir ) ],
                },
            );
        
            return $object;
        }
        
        sub gen_accessors {
            my $config = shift;
            my %args = @_;
        
            my $class = ref $config;
        
            {
                no strict 'refs';
                for my $section ( keys %{ $args{single} } ) {
                    my @vars = @{ $args{single}->{$section} };
                    for my $var ( @vars ) {
                        *{ "${class}::${section}_${var}" } = sub {
                            $config->{$section}{$var};
                        };
                    }
                }
        
                for my $section ( keys %{ $args{multi} } ) {
                    my @vars = @{ $args{multi}->{$section} };
                    for my $var ( @vars ) {
                        *{ "${class}::${section}_${var}" } = sub {
                            my $val = $config->{$section}{$var};
                            return [ $val ] unless 'ARRAY' eq ref $val;
                            return $val;
                        }
                    }
                }
            }
        
            return;
        }
        
        package main;
        
        use strict; use warnings;
        
        my $config = My::Config->new;
        
        use Data::Dumper;
        print Dumper($config->install_root, $config->template_dir);
        
        C:\Temp> cat config.ini
        [安装]
        根 = c:\opt
        
        [模板]
        目录 = C:\opt\app\tmpl
        目录 = C:\opt\common\tmpl

        输出:

        C:\Temp> g.pl
        $VAR1 = 'c:\\opt';
        $VAR2 = [
                  'C:\\opt\\app\\tmpl',
                  'C:\\opt\\common\\tmpl'
                ];

        【讨论】:

        • +1 非常有帮助——谢谢。似乎应该有一个模块可以为我们生成像template_dirs 这样的方法。我偶然发现了Config::General,尤其是它的-ExtendedAccess 选项。没用过,不过看起来很有趣。
        • @FM 我不知道Config::General。它看起来确实很有趣,但我发现有时通用解决方案可能有点过于笼统。当然,可以为标量值选项和潜在的多值选项分别自动生成访问器。
        【解决方案6】:

        冒着被笑出课堂的风险,一种解决方案是将配置存储在 XML 中(或者更冒险的是 JSON)。 Perl 之外的人工消费、互操作性,不必存在于本地 PC(XML 和 JSON 都可以从“配置 URL”请求)和一堆标准模块(XML::Simple 通常足以满足config XML 文件)存在于 CPAN 上。

        【讨论】:

          【解决方案7】:

          不要将自己束缚于一种格式 - 使用 Config::Any,或者更多的 whizbang DWIM 因子 Config::JFDI(它本身包装了 Config::Any)。有了它们,您就可以为自己购买支持 INI、YAML、XML、Apache-style config 等的能力。

          Config::JFDI 在此基础上尝试捕捉 Catalyst 配置加载器的一些魔力:将实例本地配置与应用程序范围的配置、环境变量支持和有限的宏工具(__path_to(foo/bar)__ 引入经常出人意料地方便。)

          【讨论】:

            【解决方案8】:

            对于像这样的简单配置,特别是对于我不希望这些数据在现实世界中发生变化的琐碎事情,我经常简单地使用 YAML。简单是无法击败的:

            首先,编写包含配置的 Perl 数据结构。

            use YAML;
            
            my $SETTINGS = {
                '1' => "foo",
                '2' => ["123", "456"],
                '3' => "something",
            };
            

            然后,将其传递给 YAML::DumpFile();

            YAML::DumpFile("~/.$appname.yaml", $SETTINGS);
            

            删除数据结构并替换为

            my $SETTINGS = YAML::LoadFile("~/.$appname.yaml");
            

            然后忘记它。即使您不知道或想学习 YAML 语法,也可以手动对配置进行小的更改,并且可以在 Perl 中完成更多重大更改,然后重新转储到 YAML。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2014-11-01
              • 1970-01-01
              • 2021-11-16
              • 1970-01-01
              • 1970-01-01
              • 2020-09-08
              相关资源
              最近更新 更多