这是这个正在连载发表的系列文章的第 9 章;您可以在 先前的章节中阅读关于 cfperl 的背景知识、基本原理和结构(本章主题)的所有内容。本章将对我们从“ 更佳编程之路:第七章 -- 顶级控制流和配置”开始的、关于通用 cfperl 结构的讨论作出结论。在本章中,我们将研究 groups 节解析器和缺省解析器。
groups 节是 cfengine 和 cfperl 不可或缺的部分。通过定义包含几组机器的类,可以向用户提供功能强大而又灵活的类机制。
作为全捕捉(catch-all)处理程序,缺省解析器是必要的。当 cfperl 没有专用于节的处理程序时,则将节传递给缺省解析器。
全局解析器(我在先前的章节中讨论过它)创建
cfrun 原子。
cfrun 原子是单一的配置行(例如,“add this user”),它带有附加节和类条件执行(class-conditional execution)。例如,“add user”可能在“users”节中,并有可能仅用于“linux”类。
cfrun() 函数执行这些
cfrun 原子。但是,在执行功能原子之前,
cfrun() 必需处理
import 和
group 语句。在 cfperl 的未来版本中,导入将和 cfengine 中一样可以根据条件完成。在这个版本中,导入是无条件的。
import: # import the file 'cf.extra' cf.extra groups: # declare the classes 'internal' and 'external' with # corresponding member machines internal = ( server1 server2 ) external = ( server3 server4 ) # declare the class 'primary' with member 'server5' primary = ( server5 ) # this line will be processed only when 'primary' is defined primary:: # define the 'secondary' class if we're running on a Sunday secondary = ( Sunday ) |
清单 2. 在 cfrun() 中处理 import 和 control/group 语句
foreach my $cfrun_atom ( grep { $_->{section} eq IMPORT_SECTION } @cfrun_queue)
{
dispatch(\%parsers, $cfrun_atom )
if allowed_cfrun_atom({ section => $cfrun_atom->{section},
classes => undef, actual => $cfrun_atom->{section} }, $cfrun_atom);
}
foreach my $cfrun_atom ( grep { $_->{section} eq CONTROL_SECTION ||
$_->{section} eq GROUPS_SECTION } @cfrun_queue)
{
dispatch(\%parsers, $cfrun_atom )
if allowed_cfrun_atom({ section => $cfrun_atom->{section},
classes => undef, actual => $cfrun_atom->{section} }, $cfrun_atom);
}
|
请参阅 cfperl CVS 资源库中的样本配置 cftest.conf 或用于说明导入如何在用户端工作的示例的 cfengine 样本配置(两者都在
参考资料一节中)。在 cfperl 中,由顶级解析器使用
load_file() 函数来处理导入。要简化程序流,必须首先执行导入。
在导入之后但在用户端功能原子之前处理 control 和 groups 节。例如,
control: cfengine 节定义了
actionsequence 和用户定义的类。
actionsequence 声明 cfengine/cfperl 节将拥有何种顺序。cfperl 用
cfqueue 原子实现了
actionsequence ,该原子被插入称为
@cfrun_order 的数组中。为了声明 cfengine/cfperl 稍后将为条件执行解释的类,
groups 节是必要的。
dispatch() 函数接受
cfrun 原子,并将它分派给适当的二级解析器。在本章稍后的部分中,我将解释
group 节二级解析器和空的缺省二级解析器。
任何传递到
dispatch() 函数的
cfrun 原子都具有
section 属性。
dispatch() 函数将根据节为每个原子寻找适当的解析器。例如,专用于
users: 节的解析器能理解象
add_user 这样的命令。
在没有显式地定义处理
cfrun 原子的解析器的情况下,则调用缺省解析器。缺省解析器根本不进行任何处理 — 它只是个占位符。cfperl 是显式设计的,这样它可以补充 cfengine,所以根本不应该由 cfperl 处理诸如
editfiles: 和
files: 之类的 cfengine 节。因此,缺省解析器是由
dispatch() 调用的:
$parsers{DEFAULT_SECTION()} = new Parse::RecDescent(q{ input: });
|
清单 4. dispatch() 调用适当的解析器,否则就调用缺省解析器
if (exists $parsers->{$section})
{
out (3, "dispatch: Invoking specific parser for section $section");
$retval = $parsers->{$section}->input($line);
}
else
{
die "No default parser found, quitting" unless exists $parsers->{DEFAULT_SECTION()};
out(3, "dispatch: Given section $section does not have a parser,
using default for command [$line]");
$retval = $parsers->{DEFAULT_SECTION()}->input($line);
}
|
作为有趣的副注:必须将 DEFAULT_SECTION 常数作为函数调用,否则 Perl 会认为您所指的是 字符串“DEFAULT_SECTION”而不是名为 DEFAULT_SECTION 的常数。
groups: 节的解析器会根据其它类的存在来定义类。例如,仅当已经定义“b”的情况下,“a = ( b )”才会定义“a”。cfengine/cfperl 的基于组的类定义能力具有十分强大的功能。
$parsers{GROUPS_SECTION()} = new Parse::RecDescent (q{
input: define_group
define_group: class '=' '(' class_definition ')'
{ ::define_group($item{class}, $item{class_definition}); 1; }
class_definition: class(s)
class: word
word: /\w+/
});
|
与所有其它 cfperl 解析器一样,这个解析器也有
input() 方法。这里,
input() 只能做一件事情:定义组。请记住,使用
Parse::RecDescent 模块时,所有规则同时也是方法,因此我们将
input() 看作方法。
define_group 规则是基于类和
class_definition 规则的。类只能是一个字,这个字是由 Perl 认为是字(
\w )的字符组成的,但如有必要,我们可以更改该定义。例如,如果希望连字符也成为有效的类字符,则可以将“word: /[\w-]+/”作为规则。
class_definition 规则至少由一个类名组成。这样,在 cfperl 的
groups: 节中,“groupname = ()”将不是有效的行。如果希望允许上述情况,则在规则中使用“class_definition: class(s?)”。但是我们没有这样做,因为这样没有意义。
define_group 规则使用
class 和
class_definition 来构建组定义操作。这比 cfengine 的组定义规则宽松得多,后者要求在圆括号前后留空格,因此“groupname = (linux)”是无效的。而 cfperl 将顺利地解析该行。如果希望强制 cfengine 的行为,可以使用下列代码:
define_group: class '=' '(' /\s+/ class_definition /\s+/ ')'
{ ::define_group($item{class}, $item{class_definition}); 1; }
|
但是,在我看来,不需要那种更严格的行为。
define_group 规则使用
Parse::RecDescent 数据结构来构建外部
define_group() 函数的两个参数。注:正如其名称前面的两个冒号所示,外部
define_group() 函数是全局的。
全局(因此在
groups 节解析器外部)
define_group() 函数接受两个变量。第一个是可能被定义的新类的名称,第二个是对现有类名的数组引用。
Parse::RecDescent 可以自动完成类名验证,但在我看来,外部函数更容易编码和理解。
# {{{ define_group: insert a new group into the list of defined groups,
# if one of its conditionals is defined
sub define_group($$)
{
my $group_name = shift @_ || return undef;
my $conditional_names = shift @_ || return undef;
foreach my $conditional (@$conditional_names)
{
next unless exists $classes{$conditional};
$classes{$group_name} = 1;
return $group_name;
}
return undef;
}
# }}}
|
注:“next unless”控制流以及我们调用
return() 的方式,我们对
@$conditional_names 数组进行最快的可能的遍历,然后对成功的匹配调用
return() 。
本章总结了对 cfperl 内幕的讨论。在接下来的章节中,我将讨论 cfperl 的功能,从 cfperl 的添加、删除以及更改用户和组的工具入手。
- 您可以参阅本文在 developerWorks 全球站点上的
英文原文.
-
The road to better
programming
先前的章节是:
- 更佳编程之路:简介与第 1 章 -- 开发编码指南
- 更佳编程之路:第二章 -- 注释代码
- 更佳编程之路:第三章 -- 循环、整洁代码和 Perl 语言习惯用法
- 更佳编程之路:第四章 -- 函数型编程
- 更佳编程之路:第五章 -- 模块与对象
- 更佳编程之路:第六章 -- 开发 cfperl,从头开始
- 更佳编程之路:第七章 -- 顶级控制流和配置
- 更佳编程之路:第 8 章. 顶级解析器和复合类解析器
-
cfperl 项目是由 gnu.org 主管的。
-
cfengine 主页介绍了关于 cfengine 您所需要了解的所有内容。
- 到
cfperl CVS 资源库找到 cftest.conf 和更多信息。
- 还可以获得
完整的 cfengine 配置。
- 在 developerWorks 上阅读 Ted 的
cfengine 简介。
- 下载 cfperl 项目
迄今为止的代码。
- 在 Ted 发表在
developerWorks 上的下列文章中了解关于
Parse::RecDescent的更多信息:“ 用 Perl 模块进行解析”和“ 编写说英语的 Perl 程序”。
- 此外,阅读
developerWorks上的:
- 用 Perl 进行应用程序配置
- 用 Perl 进行应用程序配置,第 2 部分
- Putting the SAX EntityResolver interface to work
- 利用 GNOME 库来简化应用编程,第 3 部分
- 在 Perl 中使用内联
- 如果您对于了解关于解析和记载(lex)的更多信息感兴趣,那么
这个记载器和解析器的 Google 目录是个不错的起点。
- 在
developerWorksLinux 专区找到更多
为 Linux 开发人员提供的参考资料。

Teodor Zlatanov 于 1999 年从美国波士顿大学(Boston University)毕业,获得计算机工程硕士学位。他从 1992 年起就从事程序员的工作,使用了 Perl、Java、C 和 C++。他的兴趣是文本解析、三层客户机-服务器数据库体系结构、UNIX 系统管理、CORBA 和项目管理方面的开放源码工作。可以通过 tzz@iglou.com与 Teodor 联系。