Perl与系统管理-1

  1. 安全的临时文件
use File::Temp qw(tempfile);
my ($fh, $filename)=tempfile();
print $fh "ba...la...ba...la...\n";
  1. UNC(Universal Naming Convention)

UNC用于在网络环境中访问文件和目录。UNC文件名中盘符和冒号变为:

\\server\sharename
  1. 处理文件系统的差异
use File::Spec;
my $path=File::Spec->catfile(qw{home cindy docs resume.doc});

use Path::Class;
my $pcfile=file(qw{home cindy docs resume.doc});
my $pcdir=dir(qw{home cindy docs});
  1. 读取HOST文件例子
open HOSTS, '<', '/etc/hosts' or die "Unable to open host file:$!\n";
my %addrs;
my %names;
while(<HOSTS>){
next if /^#/; #跳过注释行
next if /^\s*$/; #跳过空行
s/\s*#.*$//; #去除行末注释和空白符
chomp;
my($ip, @names)=split;
die "The ip address $ip already seen!\n" if (exists $addrs{$ip});
$addrs{$ip}=[@names];
foreach (@names){
die "The host name $_ already seen!\n" if (exists $names{lc $_}); #lc函数lower case;uc函数upper case。
$names{lc $_}=$ip;
}
}
close HOSTS;

得到的数据结构如下:

$addrs{'127.0.0.1'} = ['localhost'];
$addrs{'192.168.1.2'} = ['baby.org','bee1'];
$addrs{'192.168.30.2'} = ['events.com','bee2'];

$names{'localhost'} = '127.0.0.1';
$names{'baby.org'} = '192.168.1.2';
$names{'bee1'} = '192.168.1.2';
$names{'events.com'} = '192.168.30.2';
$names{'bee2'} = '192.168.30.2';
  1. 影响perl对"行"的理解
...
{ #代码块开始
local $/="-=-\n"; #重新定义行分隔符,且local限定了对变量$/的修改不会影响到{}代码块之外的内容。
...
my %rows;
while(<AF>){ #根据重新定义的行分隔符来逐行读入
chomp; #去掉 $/ = "-=-\n"
#根据对行的新的理解将输入字符串灌入哈希
%rows=split /:\s*|\n/;
}
} #代码块结束
...
  1. Perl的常见内置变量

内置变量

含义

$!

根据上下文内容返回错误号或者错误串

$”

列表分隔符

$#

打印数字时默认的数字输出格式

$$

Perl解释器的进程ID

$%

当前输出通道的当前页号

$&

与上个格式匹配的字符串

$(

当前进程的组ID$) 当前进程的有效组ID

$*

设置1表示处理多行格式.现在多以/s和/m修饰符取代之

$,

当前输出字段分隔符

$.

上次阅读的文件的当前输入行号

$/

当前输入记录分隔符,默认情况是新行

$:

字符设置,此后的字符串将被分开,以填充连续的字段

$;

在仿真多维数组时使用的分隔符

$?

返回上一个外部命令的状态

$@

Perl解释器从eval语句返回的错误消息

$[

数组中第一个元素的索引号

$\

当前输出记录的分隔符

$]

Perl解释器的子版本号

$^

当前通道最上面的页面输出格式名字

$^A

打印前用于保存格式化数据的变量

$^D

调试标志的值

$^E

在非UNIX环境中的操作系统扩展错误信息

$^F

最大的文件捆述符数值

$^H

由编译器激活的语法检查状态

$^I

内置控制编辑器的值

$^L

发送到输出通道的走纸换页符

$^M

备用内存池的大小

$^O

操作系统名

$^P

指定当前调试值的内部变量

$^R

正则表达式块的上次求值结果

$^S

当前解释器状态

$^T

从新世纪开始算起,脚步本以秒计算的开始运行的时间

$^W

警告开关的当前值

$^X

Perl二进制可执行代码的名字

$_

默认的输入/输出和格式匹配空间

$│

控制对当前选择的输出文件句柄的缓冲

$~

当前报告格式的名字

$`

在上个格式匹配信息前的字符串

$’

在上个格式匹配信息后的字符串

$+

与上个正则表达式搜索格式匹配的最后一个括号

$<

当前执行解释器的用户的真实UID

$=

当前页面可打印行的数目

$>

当前进程的有效用户ID包含正在执行的脚本的文件名

$ARGV

从默认的文件句柄中读取时的当前文件名

%ENV

环境变量列表

%INC

通过do或require包含的文件列表

%SIG

信号列表及其处理方式

@_

传给子程序的参数列表

@ARGV

传给脚本的命令行参数列表

@INC

在导入模块时需要搜索的目录列表

$-[0]和$+[0]

代表当前匹配的正则表达式在被匹配的字符串中的起始和终止的位置

  1. say的使用
use 5.0.14;  #指定版本
use feature qw(say); #这个是关键
  1. 读取/etc/passwd的内容
#!/usr/bin/perl

use feature qw(say);

@users=getpwuid $<; #读取当前用户的信息
$i=0;
foreach(@users){
say "$i: $_";
$i++;
}
  1. 对IP地址排序
my @ips = qw(
212.211.123.1
142.11.12.155
12.11.23.41
12.211.123.1
68.11.123.1
68.11.12.1
68.12.23.21
21.211.12.1
21.211.123.1
);
# Computing a single packed-string sortkey
my @sorted_ips = map
substr($_, 4) =>
sort map pack('C4' => /(\d+)\.(\d+)\.(\d+)\.(\d+)/)
. $_ => @ips;

print join("\n", @sorted_ips), "\n";

可以使用如下方法产生随机IPv4地址。

perl -le '$,=".";print map int rand 256,1..4'
  1. 管道open运行系统命令并解析回显
#!/usr/bin/perl -w

use feature qw(say);

my $hostname='www.baidu.com';
my $nsserver='114.114.114.114';
my $nslookup='/usr/bin/nslookup';

say &Lookup($hostname, $nsserver, $nslookup);

#sub procedure
sub{
my ($hostname, $nsserver, $nslookup)=@_;
my @results;

open CMD, '-|', "$nslookup $hostname $nsserver"
or die "Unable to run nslookup command: $!\n";

while(<CMD>){
next until /^Name:/;
chomp($next_line=<CMD>); #向下多取一行
$next_line =~ s/Address(es)?:\s+//;
push @results, $next_line;
}
close CMD;
join ', ', sort @results;
}
  1. 配置文件的操作
1) 注释
# This is a comment
; Ywis, eke hight thilke
2) 节
[SECTION1] # Almost anything is a valid section label
[SECTION 2] # Internal whitespace is allowed (except newlines)
[%^$%^&!!!] # The label doesn't have to be alphanumeric
[ETC. ETC. AS MANY AS YOU WANT]
3) 配置变量
name: George
age: 47

his weight! : 185
或者
name= George
age= 47
his weight! = 185
4) 在新建键值对时给定默认分割符
use Config::Std { def_sep => '=' };

使用说明

use Config::Std;

# Load named config file into specified hash...
read_config 'demo2.cfg' => my %config;

# Extract the value of a key/value pair from a specified section...
$config_value = $config{Section_label}{key};

# Change (or create) the value of a key/value pair...
$config{Other_section_label}{other_key} = $new_val;

# Update the config file from which this hash was loaded...
write_config %config;

# Write the config information to another file as well...
write_config %config, $other_file_name;
  • Config::General
    使用说明
    ​​​https://metacpan.org/pod/Config::General​​支持ini格式及Apache配置文件风格。支持在程序中读写配置。
  • Config::Scoped
    使用说明
    ​​​https://metacpan.org/pod/Config::Scoped​​支持BIND,DHCP及其他复杂配置格式的配置风格。支持在解析数据时做合法性校验,同时还能检查配置文件的读写权限。支持数据缓存功能。支持二进制存储提速。但无法在程序中动态更新配置内容。
  1. 操作XML文件
  • 基本方法
    涉及的模块
use XML::Generator;
use XML::Writer;

输出XML文件
​​​ https://metacpan.org/pod/XML::Writer​

use XML::Writer;
use IO::File;

my %hosts=(
'name'=>'agatha',
'addr'=>'192.168.1.10',
);
my $FH=new IO::File('>netconfig.xml');
or die "Unable write to netconfig.xml: $!\n";
my $xmlw=new XML::Writer(OUTPUT => $FH);
$xmlw->xmlDecl("UTF-8");
$xmlw->startTag('network');
print $FH "\n ";
$xmlw->startTag('host');
#没有设定输出顺序
foreach my $field (keys %hosts){
print $FH "\n ";
$xmlw->startTag($field);
$xmlw->characters($hosts{$field});
$xmlw->endTag;
}
print $FH "\n ";
$xmlw->endTag;
print $FH "\n ";
$xmlw->endTag;
$xmlw->end;
$FH->close();
use XML::Simple;

my $config=XMLin('config.xml');
my $config=XMLin('/etc/foo.xml', NormalizeSpace => 2);
#使用 $config->{stuff} 读取配置参数
#写入配置
XMLout($config, OutputFile => $configfile);

模块将XML转化为Perl的数据结构

<config logdir="/var/log/foo/" debugfile="/tmp/foo.debug">
<server name="sahara" osname="solaris" osversion="2.6">
<address>10.0.0.101</address>
<address>10.0.1.101</address>
</server>
<server name="gobi" osname="irix" osversion="6.5">
<address>10.0.0.102</address>
</server>
<server name="kalahari" osname="linux" osversion="2.0.34">
<address>10.0.0.103</address>
<address>10.0.1.103</address>
</server>
</config>
use Data::Dumper;
use XML::Simple qw(:strict);

my $config = XMLin(undef, KeyAttr => { server => 'name' }, ForceArray => [ 'server', 'address' ]);
print Dumper($config);
=======================================================
{
'logdir' => '/var/log/foo/',
'debugfile' => '/tmp/foo.debug',
'server' => {
'sahara' => {
'osversion' => '2.6',
'osname' => 'solaris',
'address' => [ '10.0.0.101', '10.0.1.101' ]
},
'gobi' => {
'osversion' => '6.5',
'osname' => 'irix',
'address' => [ '10.0.0.102' ]
},
'kalahari' => {
'osversion' => '2.0.34',
'osname' => 'linux',
'address' => [ '10.0.0.103', '10.0.1.103' ]
}
}
}
=================================================================
读取配置
print $config->{logdir};
print $config->{server}->{kalahari}->{address}->[1];
use XML::LibXML;
my $prsr=XML::LibXML->new();
$prsr->keep_blanks(0); #忽略空白节点
my $doc=$prsr->parse_file('config.xml');
my $root=$doc->documentElement();

#逐层遍历
my @children=$root->childNodes;
foreach my $node (@children){
print $node->nodeName()."\n";
}

#深入下一层
my $current=$children[2]; #第二个元素
@children=$current->childNodes();
$current=$children[1]; #第一个元素
print $current->textContent();
#同层的兄弟节点
$current=$current->nextSibling;

#通过TagName读取
my @interface_nodes=$current->getChildrenByTagName('interface');
my @interface_nodes=$root->getElementsByTagName('interface');

#返回所有文本子节点的内容
foreach my $node (@interface_nodes){
$node->textContent();
}

#遍历属性
foreach my $attribute ($node->attributes()){
print $attribute->nodeName.":".$attribute->getValue()."\n";
}
#读取特定属性
print $node->getAttribute('name') if $node->hasAttribute('name');

#写入
my $textnode=$node->firstChild
if ($node->firstChild->nodeType == XML_TEXT_NODE);
$textnode->setData('new info');

块信息挂载

my $root=$doc->documentElement();
my $xml2insert= <<'EOXML';
<meta>
<type name="production">This is a production network</type>
</meta>
EOXML

my $meta=$prst->parse_balanced_chunk($xml2insert);
$root->insertBefore($meta, $root->firstChild);

open my $OF, '>', $filename or die "Can't open $filename for writing: $!\n";
print $OF $doc->toString;
close $OF;

XPath方式:

my @children=$doc->findnodes('/network/*');
foreach my $node (@children){
print "$node->nodeName()\n";
}

my ($tnode)=$doc->findnodes('/network/host[2]/service[1]/text()');
print $tnode->data."\n";

foreach my $tnode ($doc->findnodes('/network/host[2]/service[1]/text()')){
print $tnode->data."\n";
}

my @multiservices=$doc->findnodes('//host[count(service)>1]');

foreach my $anode ($doc->findnodes('//host[count(service)>1]/@name')){
print $anode->value."\n";
}

#读取所有服务器并返回它们的<addr></addr>元素节点。
@nodes=$doc->findnodes('/network/host[@type="server"]//addr/text()');
foreach my $node(@nodes){
print $node->find('normalize->space(./text())')."\n";
}
或者
foreach my $node(@nodes){
print $node->textContent()."\n";
}

注意名字空间在XPath中的应用。
/x:html/x:body

  • XML::SAX
    使用SAX2(Simple API for XML version 2)解析XML。适用于数据量比较大的XML文件(不能一次性读入内存)。适用于时间驱动编程模型中。SAX2将XML文档数据当做一系列事件流来处理。

小贴士:
C的XML解析库是expat。
事件驱动编程模型中通过事件来触发处理例程(event handler)的执行。这个处理例程称回调例程(callback routine)——预定义的子程序在满足特定条件时,比如侦听到某个事件时,才“回来调用该例程”。

  • Config::Context