IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  Linux  >

更佳编程之路: 第 9 章. 类和缺省解析器

对 cfperl 内幕讨论的结论

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

Teodor Zlatanov (tzz@iglou.com), 程序员, Gold Software Systems

2003 年 7 月 09 日

cfperl 项目(用 Perl 编写的 cfengine 解释器)是自顶向下开发的。在本文中,我们将讨论组和类,以及如何处理未知输入。

这是这个正在连载发表的系列文章的第 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() 必需处理 importgroup 语句。在 cfperl 的未来版本中,导入将和 cfengine 中一样可以根据条件完成。在这个版本中,导入是无条件的。

清单 1. 样本 import 和 group 语句
  
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() 调用的:

清单 3. 缺省解析器,有点象禅语,但更为简洁
$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 的常数。





回页首


group 节解析器

groups: 节的解析器会根据其它类的存在来定义类。例如,仅当已经定义“b”的情况下,“a = ( b )”才会定义“a”。cfengine/cfperl 的基于组的类定义能力具有十分强大的功能。

清单 5. group 节解析器
$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 规则使用 classclass_definition 来构建组定义操作。这比 cfengine 的组定义规则宽松得多,后者要求在圆括号前后留空格,因此“groupname = (linux)”是无效的。而 cfperl 将顺利地解析该行。如果希望强制 cfengine 的行为,可以使用下列代码:

清单 6. 更严格的 define_group 规则
                 define_group: class '=' '(' /\s+/ class_definition /\s+/ ')'
               { ::define_group($item{class},  $item{class_definition}); 1; }
               

但是,在我看来,不需要那种更严格的行为。

define_group 规则使用 Parse::RecDescent 数据结构来构建外部 define_group() 函数的两个参数。注:正如其名称前面的两个冒号所示,外部 define_group() 函数是全局的。





回页首


define_group() 函数

全局(因此在 groups 节解析器外部) define_group() 函数接受两个变量。第一个是可能被定义的新类的名称,第二个是对现有类名的数组引用。

Parse::RecDescent 可以自动完成类名验证,但在我看来,外部函数更容易编码和理解。

清单 7. 全局 define_group() 函数
# {{{ 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 的添加、删除以及更改用户和组的工具入手。



参考资料



关于作者

Teodor Zlatanov

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




对本文的评价










回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款