快捷搜索:

更佳编程之路: 第 12 章. 使用 perledit: 段编辑文件

本文是一个连载系列文章的第 12 章。建议您涉猎本系列 曩昔颁发的章节,以懂得 cfperl 的背景常识、理论根基和布局。

犹如 上一节的主题(crontab 的治理)一样,基于 Perl 的文件编辑也是 cfperl 的紧张特性之一,除此以外没有更为抱负的措施。例如,cfengine editfiles 内嵌的措施并不具备 Perl 所供给的整个便捷的功能。cfperl 在这一 点做得异常好。从匹配和调换操作符的措施到应用外部模块,Perl 供给的所有功能在 cfperl 中都可以应用。

不幸的是,这也就意味着应用 cfperl 和应用 Perl 本身一样,会带来危险,由于用户可能会由于编辑差错而破坏一个文件。

cfper 语法应用管道来完成输入文件和输出文件的编辑。并没有应用临时文件、外部 Perl 调用或者任何其它的技术。

cfperl Perl 编辑文件以老例的 cfperl 要领来解析,先由一个依附于特定主机的“perledit”解析,重组,然后交给顶层的解析器。

什么是基于 cfperl Perl 的文件编辑?

Perl 说话主要以文本处置惩罚而驰誉。它的语法机动而简洁,可以用很少几行语句来完成繁杂的编辑操作。(参考两篇文章 -- 一行法度榜样 101和 一行法度榜样 102-- 描述了 Perl 的机动而强大年夜的编辑功能。)

cfengine 文件编辑的语法相对付 Perl 来说更为简单和直接。这平日来说是更好的,然则有一些相识 Perl 的人宁肯去用 Perl 而不去用 cfengine 内嵌的文件编辑功能。我曾经考试测验让 cfengine 的 shell 敕令和 editfiles 如我所愿地去事情,但终极的结论是,它弗成能取代 Perl。

例如,我们来斟酌一个常见的匹配 IP 地址的义务。应用 cfengine,您必须在每次用到的时刻定义一个模式来匹配一个 IP 地址。应用 cfperl,您可以这样做:

清单 1. 匹配 IP 地址

perledit:

any::

filter in place /etc/hosts 'use Regexp::Common qw/net/; next unless m/$RE{net}{IPv4}/;'

这一行代码使 cfperl 打开并读入 /etc/hosts 文件,然后再只写回那些匹配某个 IPv4 IP 地址的那些行。相对照而言,cfengine 有 DeleteLines 函数 (删去某些行,那些行或者包孕某个词,或者匹配某个正则表达式,或者以某个词开首)。问题是,您不得不自己去写匹配 IPv4 地址的正则表达式。您可能会说,这很简单,不过是重复4次 d+ ,不要着末的句号,对吗?不完全对;IP 地址数据的范围是 0 到 255,假如您想要对您的 B 类子网中的所有地址进行特殊处置惩罚怎么办?应用 cfperl, Net::Netmask 可以帮您完成这项义务。假如用 cfengine,这个义务是异常艰苦的,而且您还很可能会掉足。

我强烈建议您去看一看 CPAN 上的 Regexp::Common 模块 (参阅 参考资料一节的链接)。它不仅与 cfperl 有关,而且对付任何一个真正的 Perl 法度榜样员来说它都是一个基础的对象。

以下是 4 个用于文件编辑的 cfperl 敕令,对应于文件编辑的 4 个基础功能:

写文件 ( write )

以另一个文件作为数据源写一个文件 ( write from A to B )

以一个正则表达式来过滤一个文件 ( filter in place )

将一个文件过滤到另一个文件 ( filter A to B )

filter 敕令犹如 perl -p ,会自动打印出敕令的结果。 write 敕令犹如 perl -n ,由用户在适当的机会调用 print() 再打印。假如您认识 -p 和 -n 的用法,您将会感觉应用 cfperl 的文件编辑敕令异常轻车熟路。假如您对这些不认识,请参考 "perldoc perlrun" 赞助手册。

如您所见,cfperl 解析器定义了保存文件信息的数据布局,尤其是文件模式 (输入或输出) 和类型 (文件或管道)。此外,出于 IO::Pipe 模块的要求,说冥器在应用到管道敕令的时刻还去掉落了文件名中的引号和管道 ( | ) 操作符。

当定义好文件名规则后,其它的工作就好办了:

清单 3. perledit 解析器的另外部分

input: filter_in_place | write_in_place |

filter_from_to | write_from_to |

filter_in_place: /filter/ /in/ /place/ output_regular_filename command

{::edit_op(::EDIT_IN_PLACE, $item{command}, undef, $item{output_regular_filename}); 1; }

filter_from_to: /filter/ /from/ input_filename /to/ output_filename command

{::edit_op(::EDIT_FROM_TO, $item{command}, $item{input_filename}, $item{output_filename});

1; }

write_in_place: /write/ output_filename command

{::edit_op(::WRITE_IN_PLACE, $item{command}, undef, $item{output_filename}); 1; }

write_from_to: /write/ /from/ input_filename /to/ output_filename command

{::edit_op(::WRITE_FROM_TO, $item{command}, $item{input_filename}, $item{output_filename});

1; }

command: /'.*'/

{

$item[1] =~ s/^'(.*)'/$1/;

$return = $item[1];

1;

}

敕令 规则撤除了引号。留意,不管是单引号照样双引号都被撤除了,由于它是一个贪婪的匹配,不停到处置惩罚到着末一个单引号。

在开始处,对应于四个基础敕令定义了平日的 输入 文件规则。独逐一个不用 input_filename 或 output_filename 的规则是 filter_in_place ,它不能应用管道,由于它不用将过滤后的内容从新写回。

以机动的要领打开文件:

edit_open_file() 函数

edit_open_file() 函数为 edit_op() 函数打开文件。

清单 5. edit_open_file() 函数

sub edit_open_file

{

my $data = shift @_;

my $mode = shift @_ || $data->{mode};

my $to_open;

if ($data->{type} eq EDIT_FILETYPE_FILE)

{

$to_open = $data->{file};

$data->{object} = new IO::File;

# if the output file has not been prepended with a > already...

if ($mode eq EDIT_OUTPUT && $to_open !~ m/^>/)

{

$to_open = ">$to_open";

}

$data->{object}->open($to_open);

out(5, "edit_open_file: opened file '$to_open'");

}

elsif ($data->{type} eq EDIT_FILETYPE_PIPE)

{

$to_open = $data->{file};

$data->{object} = new IO::Pipe;

if ($mode eq EDIT_INPUT)

{

$data->{object}->reader($to_open);

}

elsif ($mode eq EDIT_OUTPUT)

{

$data->{object}->writer($to_open);

}

}

else

{

warn "edit_open_file: Invalid input/output description passed in " . Dumper($data);

}

unless ($data->{object}->opened())

{

out(0,

"edit_open_file: could not open " . $data->{type} .

" '$to_open', editing operation will fail");

return undef;

}

return $data;

}

然后, edit_op() 设置接接下来会用的逻辑操作模式。这看起来违反常理。您可能会问,为什么不在代码中应用常量呢?缘故原由是它们对付一样平常读者来说太难解,而且在当每个前提都必要额外的思虑时很轻易让法度榜样员 (可能便是您的) 犯差错。

清单 7. 设置操作模式

my $in_place_mode = ($op == EDIT_IN_PLACE) || ($op == WRITE_IN_PLACE);

my $from_to_mode = ($op == EDIT_FROM_TO) || ($op == WRITE_FROM_TO);

my $edit_mode = ($op == EDIT_IN_PLACE) || ($op == EDIT_FROM_TO);

my $write_mode = ($op == WRITE_IN_PLACE) || ($op == WRITE_FROM_TO);

my $write_once_mode = 0;

if ($write_mode && $in_place_mode)

{

$write_once_mode = 1;            # there is no loop for this case

}

然后, edit_op() 履行文件编辑主轮回。 outputfilehandle 保存在 $old_handle 中,这样在编辑轮回内部调用 print() 就可以将其传送到另一个文件而不是传送到默认的 STDOUT 。您可以看到,我们前面定义的具体的模式相对付它们代表的常量来说更轻易理解。这办理的基础问题是,调用常量是顶层操作模式参数,而它们的内容 (操作子模式,假如您要用到) 才是法度榜样真正必要的。例如,我们实际上不关心 WRITE_IN_PLACE 模式的应用。我们关心的是 $write_once_mode 模式被激活。

清单 8. 文件编辑主轮回

my $old_handle = select;         # save the old output destination

eval

{

no strict;

no warnings;

if ($edit_mode && $in_place_mode)    # filter in place

{

...

}

elsif ($write_once_mode)        # "write" command

{

...

}

else                  # "write from to" or "edit from to"

{

...

}

};

select $old_handle;           # restore output in case it's needed

edit_op() 的另外部分完成各个模式响应的功能。对 filtering in place 来说,输入文件被作为各行的一个列表读入并处置惩罚。这是由于像 File::Temp 那样应用临时文件,不像我所等候那样靠得住。它或者轻易掉足,或者很难用。

由 $write_once_mode 变量指定的 write 模式是别的一种特殊的环境。在这里没有轮回;敕令的输出被简单地写入到输出文件中。

着末是两个 from-A-to-B 模式。两者独一的差别是,在 filter from A to B 模式中,在每一行处置惩罚完成后要履行一个附加的 print() 语句。

停止语

cfperl 的基于 Perl 的文件编辑能力恰是系统治理的另一个难题。我盼望这些能对您有所赞助!

编写法度榜样的乐趣在于探索和发明;cfperl 的编辑功能将使您乐此不疲。应用 cfengine editfiles 语法很难或者根本不能实现的编辑义务,您用 cfperl 都可以完成。不仅如斯,这使得编辑成为一项次要事情,终极,对 cfperl 履行历程中随意率性 Perl 代码来说,编辑将成为例行公事的步骤。开始投入吧!祝您获得更多乐趣!

您可能还会对下面的文章感兴趣: