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

developerWorks 中国  >  Linux  >

更佳编程之路: 第 11 章. 利用 cfperl 进行 crontab 管理

利用 cfperl 轻松地添加和删除 crontab 项

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

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

2003 年 7 月 09 日

在本系列文章中,Ted 从头到尾完整地开发了 cfperl 项目 — 它只是一个用 Perl 编写的 cfengine 解释器。在本文中,他讨论了“cron”这一部分,可在其中方便地添加或删除 crontab 项。

正如文章标题所示,本文是正在连载的系列文章中的一部分。建议您阅读本系列 以前发表的章节,以了解 cfperl 的背景知识、理论基础和结构。

使用 cfperl 进行 crontab 管理是很理想的,这样就可以用纯英语来描述 crontab 任务。cfperl 语法可以用一行来描述标准 crontab 中要用多行描述的内容(例如,那些在 5:30 和 6:00 运行的任务)。cfperl 使用标准的 cron 扩展来实现其 crontab 功能。

cfperl crontab 部分是用标准 cfperl 方式解析的,通过顶级解析器将“cron”部分指向特定的主机、组等等。

什么是 crontab?

cron 包由两个程序构成:定期执行任务的 cron 守护程序,以及修改个人用户的 cron 任务列表的 crontab 程序。这两个程序非常有用,如果您使用过 UNIX 系统,那么您可能听说过 crontab。用户的 cron 任务列表被称为 crontab,它就象用来修改该列表的程序一样。我将讨论标准的 cron 包,但不会讨论由于 cron 重写而添加的所有扩展。经过改进的 cron 包(如 anacron、fcron 和 ucron)要优于标准的 cron,但遗憾的是,它们都对标准的 crontab 格式进行了非标准的扩展。

在典型的 UNIX 风格中,标准的 crontab 格式非常有效率,但是非常不便于阅读。六个字段(分钟、小时、日、月、星期几和命令)是用空格分隔的。每个字段都有一个列数字或“*”;例如小时字段中的“0,1,2”意味着该命令应当在午夜 0 点、1 点和 2 点运行。当某个字段中出现“*”时,这意味着 cron 应当匹配该字段中的任何内容 — 任何小时、任何天、任何月等等。另外一个复杂之处在于,星期几是从 0 开始计数的(星期天是 0),而每月的天数是从 1 开始计数的(从 1 到 31)。

星期几字段稍微有点复杂,因为它不考虑日字段。如果日是“*”而星期几是“0”,那么命令将只在星期天运行。如果日是“1”,而星期几是“*”,那么命令将只在该月的第一天运行。但是,如果日是“5”而星期几是“2”,那么命令将在该月的第 5 天和星期二 运行。

幸运的是,cfperl 使您无须记住所有那些东西 — 除非您对那类问题很感兴趣,如果是那样的话,cfperl 的 crontab 功能对您而言是毫无用处的,也不必用它来解决问题。它不是一种“爱出风头”的软件。





回页首


在使 crontab 更易于管理方面,cfperl 能做些什么?

利用 cfperl,可以按照便于阅读的规范生成 crontab。例如:

hourly at 0,10,20,30,40,50 do as cftest /usr/bin/synchronize

很简单,是吗?您必须做的所有工作就是指定作业执行的分钟字段,以及将编辑哪个用户(在本例中是“cftest”)的 crontab。这很容易地转换成了一行 crontab:

0,10,20,30,40,50 * * * * CFPERL=1 /usr/bin/synchronize

请注意“CFPERL=1”部分;它向后续运行的 cfperl 指出:该行是由 cfperl 生成的,可以重新生成。

范围怎么样呢?例如,为 cfperl 提供的分钟范围“23-43,44-49”,在 crontab 中将生成“23-49”。





回页首


用户规范解析器

用于 crontab 项用户规范的解析器是 %parsers 散列(带有键“cron”)的成员。按照 cfperl 的惯例,cfperl 配置中的“cron”部分将调用该解析器。

该解析器可以处理三种请求:删除(delete)、删除全部(deletfull)和常规(regular)crontab 请求。如果 cfperl 要清除用户的 crontab 中的旧项或全部 crontab 项,则可以使用删除。

cfperl“cron”解析器中的 crontab 请求是根据频率规范(“yearly”、“monthly”、“daily”和“hourly”)构建的。

清单 1. 顶级“cron”解析器频率规范
frequency: yearly | monthly | weekly | daily | hourly
hourly: /hourly/i /at/i minute { { minute => $item{minute} } }
daily: /daily/i /at/i hour { { hour => $item{hour} } }
weekly: /weekly/i weektime_spec_list { { weektime => $item{weektime_spec_list} } }
monthly: /monthly/i monthtime_spec_list { { monthtime => $item{monthtime_spec_list} } }
yearly: /yearly/i yeartime_spec_list { { yeartime => $item{yeartime_spec_list} } }

其余规则相当简单,这些规则提供了几乎所有种类的递归事件的详尽声明。但是,数字项和范围的规范略微复杂,需要在专门的一节中进行介绍。





回页首


数字项和范围

针对“cron”部分的解析器需要理解任何数字范围或项。这包括小时,由于分钟是额外的负担,因此解析小时稍微有点困难。

清单 2. 数字项和范围规范
               minute: numeric_list
hour:   numeric_hour_list
day:    numeric_list
month:  numeric_list
weekday: numeric_list
numeric: numeric_range | numeric_single
numeric_single: /\d+/
numeric_hour: /\d+/ ':' /\d\d/ 
              { { hour => $item[1], minutes => $item[3] } }
              | numeric_single 
              { { hour => $item{numeric_single}, minutes => 0 } }
numeric_range: numeric_single '-' numeric_single 
{ $return = [ ($item[1] .. $item[3]) ]; 1; }
numeric_hour_list: numeric_hour ',' numeric_hour_list
                 { $return = [ @{$item{numeric_hour_list}},
                               (ref $item{numeric_hour} eq 'ARRAY') ? 
                                @{$item{numeric_hour}} : $item{numeric_hour} ];
                    1; }
               | numeric_hour
                 { $return = [ (ref $item{numeric_hour} eq 'ARRAY') ? 
                               @{$item{numeric_hour}} : $item{numeric_hour} ];
                 1; }
               | '*'
numeric_list: numeric ',' numeric_list
                 { $return = [ @{$item{numeric_list}},
                               (ref $item{numeric} eq 'ARRAY') ? 
                                @{$item{numeric}} : $item{numeric} ]; 
                    1; }
               | numeric
                 { $return = [ (ref $item{numeric} eq 'ARRAY') ? 
                                @{$item{numeric}} : $item{numeric} ]; 1; }
               | '*'
               

用于“numeric_list”和“numeric_hour_list”规则的解析器规范是递归的。规则取决于其本身或 终结符,这意味着最终它必须找到匹配规则交替(rule alternation)的文本,该规则交替不取决于规则本身。

如果觉得这让您困惑,那么请考虑一列数字,例如“5,6,7”。您可以将该列表定义成“(1)一个数字,后面跟着一个逗号,逗号后面再跟着一个数字列表,或者(2)数字本身”。对于解析一列数字的一般规则,规则(1)和规则(2)是可以交替使用的解决方案。但是仅使用规则(2)是无用的,因为它只理解单个数字。另一方面,规则(1)不足以处理列表结尾处的问题,因为其尾端无逗号。如果要递归地解析数字列表,那么必须交替使用规则(1)和规则(2)。

解析项列表有其它许多方法,但是利用 Parse::RecDescent (它是递归的子代解析器),递归方法的效果是最佳的。它还是一种非常完美的方法,用几行就概括了对列表的所有可能的解释。请参考 Parse::RecDescent 文档,以获取更多有关递归解析器和规则的信息(请参阅 参考资料以获取链接)。

复杂性的另一面在于平铺(flatten)数组引用的代码。在某些地方,如果项的内容是数组引用,那么它们会被区别对待。这是因为将数组引用平铺成列表可以在解析器中完成。

数组引用本应当保留在解析器输出中,将由使用该解析器的代码进行解释。但是,将引用平铺成列表只会使解析器变得更为复杂一些,而必须处理作为输出的引用的代码则会变得相当复杂。

我们感兴趣的一个规则就是“星期几”规则。

清单 3. 星期几
              weekday_name: 'Monday' { 1 } | 'Tuesday' { 2 } | 'Wednesday' { 3 } | 
              'Thursday' { 4 } | 'Friday' { 5 } | 'Saturday' { 6 } | 
              'Sunday' { $return = 0; 1; }
              

该规则对 Sunday 进行区别对待,因为在 crontab 格式中它是 0。这很适合 crontab,但对于解析器而言却很槽糕。如果简单地用 0 表示星期天,那么解析器会认为 0 表示解析出错。因此,我们将 $return 变量设为 0,但是根据规则却返回 1。这是 Parse::RecDescent 的一种常见且危险的隐患,其中,如果根据规则直接返回,那么正确解析的 0 就会被认为是一种错误。





回页首


用于 crontab 生成的 Perl 功能

“cron”解析器的所有输出都被传递给 cron_op() 函数。我们对辅助函数 get_cron_lines()put_cron_lines()get_cron_command() 不感兴趣;这些函数只是读或写 crontab,并获取正确的方法以调用 crontab 命令(正如前面讨论的,该命令由 cron 守护程序使用)。

通过使用 make_cron_line() 来执行机械的工作, construct_cron_line() 函数构建了一行实际的 cron 项。

extract_yearly_intervals()extract_weekly_or_monthly_intervals()extract_daily_intervals() 函数使用 reduce_interval() 来获取其时间间隔的最短表示,但是,即使可以使用“*”,它们也不使用该符号。这不是一个特性,也不是一个错误,但在以后会得到修复。

此外, extract_daily_intervals() 尝试合并时间间隔。这意味着,如果用户要求某个命令在每天上午 5:30 和下午 3:30 执行,那么 extract_daily_intervals() 将认为只要一行 crontab(“30 5,15 * * * COMMAND”)就足以实现用户的请求。不相容的时间间隔(例如上午 3:30 和下午 4:20)将作为单独的 crontab 行完成。现在还无法修复该问题;它是标准的 crontab 格式所带来的基本问题。

我将只合并每天的时间间隔。尽管也可以合并每星期、每月和每年的时间间隔,但是我觉得现在不值得花工夫去研究。但是肯定是可以完成。





回页首


结束语

cfperl 的 crontab 能力是解析用户输入和生成用标准格式表示的 crontab 项的简单组合。通过 Parse::RecDescent 的解析能力,cfperl“cron”部分语法支持各种自由格式的、用于递归事件的用户规范。该语法是可扩展的,并且也便于维护。

我们做了两个折衷。一个折衷是当生成新的 crontab 时,丢弃以前由 cfperl 生成的 crontab 行。实际上,每次都会重新生成由 cfperl 生成的那部分 crontab。除了 cfperl 运行时花费了一些额外的处理时间之外,这样做对于用户并没有显著的影响。可替代的方法是使用顺序标记、校验和或其它标识措施来识别需要重新生成的 crontab 行,但是该方法有两个问题。第一个问题就是该方法是一种投入小于产出的方法。另一个问题就是由 cfperl 生成的旧项会使 crontab 变得杂乱无章,其结果是:已经不再需要某些旧项,但却没人敢把它们除去。

第二个折衷就是不合并一天以上的时间间隔。这意味着不会自动将某个规范,例如“monthly on 1 at 2; on 4 at 2”,合并成一条“monthly on 1,4 at 2”规范。可以添加这一功能,但是它唯一的好处是减少生成的 crontab 行数,其回报相对于必须花费的额外工作似乎不划算。



参考资料



关于作者

Teodor Zlatanov

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




对本文的评价










回页首


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