首先是获取主机名的方式,Perl提供了Sys::Hostname
模块,能够查询当前的主机名:mysql
use Sys::Hostname; print hostname, "\n";
Perl中提供了下面一大堆的内置函数用来获取用户、组、网络相关的信息。这些perl函数在C中也都有对应的函数。sql
# 获取和设置用户和组 endgrent - be done using group file endpwent - be done using passwd file getgrent - get next group record getgrgid - get group record given group user ID getgrnam - get group record given group name getlogin - return who logged in at this tty getpwent - get next passwd record getpwnam - get passwd record given user login name getpwuid - get passwd record given user ID setgrent - prepare group file for use setpwent - prepare passwd file for use # 获取和设置网络信息 endhostent - be done using hosts file endnetent - be done using networks file endprotoent - be done using protocols file endservent - be done using services file gethostbyaddr - get host record given its address gethostbyname - get host record given name gethostent - get next hosts record getnetbyaddr - get network record given its address getnetbyname - get networks record given name getnetent - get next networks record getprotobyname - get protocol record given name getprotobynumber - get protocol record numeric protocol getprotoent - get next protocols record getservbyname - get services record given its name getservbyport - get services record given numeric port getservent - get next services record sethostent - prepare hosts file for use setnetent - prepare networks file for use setprotoent - prepare protocols file for use setservent - prepare services file for use
从动做上分为3类:shell
getXXXXent
表示从对应文件中读取每一行并迭代,这个过程当中间会隐式打开对应文件(因此隐式地维护了一个文件句柄)并使用指针指向每次读取完的位置,可使用setXXXent
来重置指针使其指向文件开头(也就是说从头开始迭代),使用endXXXent
来关闭隐式打开的文件句柄(有些函数会经过网络解析获得结果,如DNS解析,endXXXent也会关闭这个网络链接从而终止解析过程)。编程
这里共有18个函数。包括如下几个文件(以Linux操做系统为例):数组
经过getXXXent
迭代这些文件里的每一行,都获得一个XXXent结构体对象。例如/etc/group
中的每一行对应一个grent对象,这一行中的各个字段是一个grent结构体的各个字段。bash
这里6种结构体对象包含的字段以下:网络
# 0 1 2 3 4 ( $name, $passwd, $gid, $members ) = getgr* ( $name, $aliases, $addrtype, $net ) = getnet* ( $name, $aliases, $port, $proto ) = getserv* ( $name, $aliases, $proto ) = getproto* ( $name, $aliases, $addrtype, $length, @addrs ) = gethost* ( $name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell, $expire ) = getpw* # 5 6 7 8 9
这种结构体对象并不是只能从这些文件中获取,本身也能够构建或经过其它函数返回,相关内容在后面介绍中会提到。less
除了上面的分类,用户、组还能继续分为:dom
获取网络信息的操做还有:ssh
这些函数在标量上下文中返回单个name或id类数据,在列表上下文中返回各自的ent结构体对象。
虽然看着一大堆,分类后其实很容易记忆。可是,这些函数都不是很方便,由于有很多函数里的参数或返回值是须要或通过了二进制打包的。好比gethostbyaddr
函数的参数是一个ip地址,但不能直接传递"192.168.100.12"这样的地址,而是先使用pack()或使用Socket模块提供的inet_aton()
将其转换成二进制格式,再传递给gethostbyaddr
。
正是由于这些函数使用起来并不方便,因此每种类型(passwd、group、host、network、service、protocol)都有对应的面向对象的模块,使用这些模块中的方法,能够免去转换的过程。它们对应的模块为:
因为不少函数在用法上是很是相似的,因此在后文介绍重复内容时仅将简单说明重复函数的用法。
相关的函数和模块有:
首先解释getgrent以及grent结构对象,由于grent结构将group相关的东西全都串起来了。在后文也都按照这种方式介绍每一种分类。
getgrent
函数会遍历/etc/group文件,并从中读取每一行,每次遍历到一行都放进一个称为grent
的结构对象中。前文列出过各类结构对象包含的字段,其中grent结构包括以下字段:
# 0 1 2 3 ( $name, $passwd, $gid, $members ) = getgr*
例如,/etc/group中的前三行为:
$ head -n 3 /etc/group root:x:0:test1,test2,test3 daemon:x:1: bin:x:2:
使用getgrent()
取前3行:
#!/usr/bin/perl use strict; use warnings; use 5.010; my $i; while(my ($name, $passwd, $gid, $members) = getgrent) { $i++; last if $i == 4; say "group name: $name"; say "group passwd: $passwd"; say "group id: $gid"; say "group member: $members"; say "-" x 10; }
执行结果:
group name: root group passwd: x group id: 0 group member: test1 test2 test3 ---------- group name: daemon group passwd: x group id: 1 group member: ---------- group name: bin group passwd: x group id: 2 group member: ----------
前面说过,getXXXent()
会隐式地打开对应的文件并维护一个对应的文件句柄,同时会使用指针标记好已读取到哪一行。可使用setgrent()
重置指针使其回到文件头部(也就是从头开始迭代),使用endgrent()
关闭隐式维护的文件句柄,固然,再次调用getXXXent()
会再次打开文件句柄。
例如,读取两个组后,绕回到开头再读两个组,在这过程当中打开的文件并无关闭。而后使用endgrent关闭已打开的文件句柄。最后再次读取两个组,这会从新打开文件,直到程序退出后文件被关闭。
#!/usr/bin/perl # use strict; use warnings; use 5.010; say join ', ', getgrent; say join ', ', getgrent; setgrent; say join ', ', getgrent; say join ', ', getgrent; say '-' x 30; system 'lsof -n | grep "group"'; endgrent; say '-' x 30; system 'lsof -n | grep "group"'; say '-' x 30; say join ', ', getgrent; say join ', ', getgrent;
执行结果:
root, x, 0, test1 test2 test3 bin, x, 1, root, x, 0, test1 test2 test3 bin, x, 1, ------------------------------ systemd 1 root 6r DIR 0,21 0 1148 /sys/fs/cgroup/systemd perl 70014 root 3r REG 8,2 721 35750228 /etc/group ------------------------------ systemd 1 root 6r DIR 0,21 0 1148 /sys/fs/cgroup/systemd ------------------------------ root, x, 0, test1 test2 test3 bin, x, 1,
使用getgrnam()、getgrgid()
也能够获取相关组信息。在标量上下文下,前者返回gid,后者返回group_name。在列表上下文,二者都返回grent结构:
#!/usr/bin/perl use strict; use warnings; use 5.010; my $gr_gid = getgrnam 'root'; say "gr_gid: $gr_gid"; say '=' x 10, ' scalar context ', '=' x 10; my $gr_name = getgrgid 0; say "gr_name: $gr_name"; say '=' x 10, ' list context ', '=' x 10; my ($name, $passwd, $gid, $members) = getgrnam 'root'; say "name: $name"; say "passwd: $passwd"; say "gid: $gid"; say "members: $members";
执行结果:
gr_gid: 0 ========== scalar context ========== gr_name: root ========== list context ========== name: root passwd: x gid: 0 members: test1 test2 test3
获取用户信息和组信息的方式是同样的,相关函数为getpwent
、setpwent
、endpwent
、getpwnam
、getpwuid
,惟一不同的是pwent结构对象的字段和grent结构对象的字段不同。
# 0 1 2 3 4 $name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell, $expire # 5 6 7 8 9
须要注意的是,不一样操做系统中支持的字段不同。
#!/usr/bin/perl use strict; use warnings; use 5.010; my $user_uid = getpwnam 'root'; say "user_uid: $user_uid"; say '=' x 10, ' scalar context ', '=' x 10; my $user_name = getpwuid 0; say "user_name: $user_name"; say '=' x 10, ' list context ', '=' x 10; say join ', ', getpwnam 'root';
结果:
user_uid: 0 ========== scalar context ========== user_name: root ========== list context ========== root, 这里是root的密码, 0, 0, , , root, /root, /bin/bash
getlogin()
函数能够获取当前用户名。
#!/usr/bin/perl use strict; use warnings; use 5.010; say getlogin;
root用户执行结果:
$ perl login.pl root
这两个模块提供了面向对象方式的操做。导入这两个模块,里面的函数默认会覆盖同名内置函数get{pw | gr}{ent | nam | gid},即Core
模块中的这些函数,而且模块中的这些函数会返回对应的对象。
# grent OO my $gr = getgrnam 'root' or die "no root group"; # pwent OO my $pw = getpwuid 0 or die "no uid=0 user";
经过这些对象,能够直接获取到给定字段的值,且在导入了:FIELDS
标签后,能够直接使用变量来访问对应字段。例如:
# grent OO 方法 等价变量 ------------------------ $gr->name $gr_name $gr->gid $gr_gid $gr->passwd $gr_passwd $gr->members $gr_members # pwent OO 方法 等价变量 ------------------------ $pw->name $pw_name $pw->passwd $pw_passwd $pw->uid $pw_uid $pw->gid $pw_gid $pw->quota $pw_quota $pw->comment $pw_comment $pw->gecos $pw_gecos $pw->dir $pw_dir $pw->shell $pw_shell
同时,还各自提供了getpw()和getgr()方法,这两个方法是getpwuid()、getpwnam()、getgrgid()、getgrnam()的多态方法,它根据参数类型判断调用哪一个函数。例如给getpw()传递数值时,表示调用getpwuid(),传递字符串时表示调用getpwnam()。
例如:
#!/usr/bin/perl use strict; use warnings; use 5.010; use User::pwent qw(:FIELDS); use User::grent qw(:FIELDS); my $gr = getgrnam 'root' or die 'no root group'; my $pw = getpwuid 0 or die 'no uid=0 user'; # grent OO say '=' x 10 , ' grent OO ', '=' x 10; say $gr->name; say $gr_name; say $gr->gid; say $gr_gid; say '=' x 10 , ' pwent OO ', '=' x 10; # pwent OO say $pw->name; say $pw->shell; say $pw_dir;
执行结果:
========== grent OO ========== root root 0 0 ========== pwent OO ========== root /bin/bash /root
通过前面grent和pwent的解释以后,再理解hosts、net、serv、proto就简单多了。
hosts相关函数和模块:
gethostent
、gethostbyaddr
和gethostbyname
用来给给定的主机名或地址进行DNS解析。它会读取/etc/hosts文件,也会从网络上作DNS解析。
在开始解释这些函数以前,先了解些必要基础知识。
在/etc/hosts文件中包含了本地的DNS解析记录,例如:
# IP_address canonical_hostname [aliases...] 127.0.0.1 localhost localhost.localdomain ::1 localhost localhost.localdomain 192.168.100.12 www.longshuai.com www1.longshuai.com
其中第一列是IP地址,第二列是该IP地址对应的规范主机名(canonical_hostname),从第三列开始全都是主机别名(aliases),即DNS里的CNAME记录。
若是将上面的www.longshuai.com
换成DNS资源记录,区域数据文件中对应的配置以下:
www.longshuai.com. IN A 192.168.100.12 www1.longshuai.com. IN CNAME www.longshuai.com.
也就是说,当查询www.longshuai.com
时,会获得其对应的IP地址192.168.100.12
,当查询别名www1.longshuai.com
时,先解析回规范主机名www.longshuai.com
,再获得其对应的A记录,即192.168.100.12
。
例如,使用host
命令(或nslookup、dig命令)解析一下www.baidu.com
相关的记录:
$ host www.baidu.com www.baidu.com is an alias for www.a.shifen.com. www.a.shifen.com has address 183.232.231.172 www.a.shifen.com has address 183.232.231.174
结果说明了,www.baidu.com
只是www.a.shifen.com
的一个别名,这个主机名有两个A记录,IP地址分别是183.232.231.172
和183.232.231.174
。
和前面介绍的grent、pwent同样,hostent也是一个结构体对象,该结构体对象包含以下字段:
# 0 1 2 3 4 ( $name, $aliases, $addrtype, $length, @addrs )
其中:
$aliases
中可能会包含多个别名,它是一个数组,但必须使用$aliases
而非@aliases
,不然后面几个返回值都被收集到@aliases中
$addrtype
是地址类型,如今支持AF_INET和AF_INET6两种类型$length
是长度,ipv4地址长度都是4字节,因此值为4,ipv6是16字节@addrs
是以二进制方式打包的地址列表,没法直接输出,须要将每一个地址元素都解包。解包的方式有两种:
for (@addrs) { my ($a, $b, $c, $d) = unpack 'C4', $_ }
for (@addrs){ my $addr = inet_ntoa($_) }
看下面的例子就知道了。
例如,下面使用gethostent()获取/etc/hosts中的每个结构对象:
#!/usr/bin/perl # use strict; use warnings; use 5.010; while(my ($name, $aliases, $addrtype, $length, @addrs) = gethostent){ say "name: $name"; say "aliases: $aliases"; say "addrtype: $addrtype"; say "length: $length"; say "addrs: @addrs"; say '-' x 10; }
结果:
name: localhost aliases: localhost.localdomain localhost4 localhost4.localdomain4 addrtype: 2 length: 4 addrs: ---------- name: localhost aliases: localhost.localdomain localhost6 localhost6.localdomain6 addrtype: 2 length: 4 addrs: ---------- name: www.longshuai.com aliases: www1.longshuai.com addrtype: 2 length: 4 addrs: ----------
可见,第四个字段是没法直接输出的,须要将其解包。可使用unpack()或Socket模块中的inet_ntoa():
# 使用inet_ntoa()将二进制打包的地址转换成点分十进制的IP use Socket qw(/inet/); for (@addrs){ say "addrs: ", inet_ntoa $_; } # 使用unpack()解包 for (@addrs){ say "addrs: ", join '.', unpack 'C4', $_; }
再看看gethostbyname()
,只要给一个主机名,就能够解析出别名以及相关A记录。在列表上下文它返回hostent结构对象,在标量上下文,它返回打包好的IP地址。
#!/usr/bin/perl use strict; use warnings; use Socket qw(/inet/); use 5.010; # scalar context my $addr = gethostbyname $ARGV[0]; say "addr: ", inet_ntoa $addr; # list context my ($name, $aliases, $addrtype, $length, @addrs) = gethostbyname $ARGV[0]; say "name: $name"; say "aliases: $aliases"; say "addrtype: $addrtype"; say "length: $length"; for (@addrs){ say "addrs: ", inet_ntoa $_; }
执行:
$ perl host.pl www.baidu.com addr: 183.232.231.174 name: www.baidu.com aliases: addrtype: 2 length: 4 addrs: 183.232.231.174 addrs: 183.232.231.172 $ perl host.pl www.perl.org addr: 151.101.42.217 name: dualstack.osff.map.fastly.net aliases: www.perl.org cdn-fastly.perl.org addrtype: 2 length: 4 addrs: 151.101.42.217
若是使用gethostbyaddr()
,那么其IP地址参数须要使用inet_aton()或unpack()进行打包转换。但注意,通常gethostbyaddr()只用于解析/etc/hosts文件中有的地址。
#!/usr/bin/perl use strict; use warnings; use Socket qw(/inet/ AF_INET); use 5.010; # scalar context my $host = gethostbyaddr(inet_aton('192.168.100.21'), AF_INET); say "host: ", $host; # list context my ($name, $aliases, $addrtype, $length, @addrs) = gethostbyaddr(inet_aton('192.168.100.21'), AF_INET); say "name: $name"; say "aliases: $aliases"; say "addrtype: $addrtype"; say "length: $length"; for (@addrs){ say "addrs: ", inet_ntoa $_; }
在使用gethostbyname和gethostbyaddr的时候,解析以后必定要判断返回结果是否存在,即$name
是否为undef。以下是一个简单的DNS A记录解析系统:
#!/usr/bin/perl use strict; use warnings; use Socket qw(/inet/); use 5.010; die "Give me a hostname" unless @ARGV; while(my $lookup = shift @ARGV){ my ($name, $aliases, $addrtype, $length, @addrs) = gethostbyname $lookup; # must check $name defined or not if($name){ foreach(@addrs){ $_ = inet_ntoa($_); } if ($name eq$lookup){ print "$lookup IP address: @addrs\n"; } else { print "$lookup (real name $name) IP address: @addrs\n"; } } else { print "host $lookup not found\n"; } }
执行:
$ perl host.pl www.sina.com www.sina.com (real name wwwus.sina.com) IP address: 66.102.251.33 $ perl host.pl www.cnblogs.com www.baidu.com www.cnblogs.com IP address: 42.121.252.58 www.baidu.com IP address: 183.232.231.174 183.232.231.172
同User::pwent
和User::grent
模块同样,Net::hostent
提供了与内置函数gethostent、gethostbyname、gethostbyaddr同名的面向对象的方法,使得不须要中间繁琐的打包、解包过程。默认导入该模块时会覆盖这几个内置函数,若是想要引用这几个函数,能够加上CORE::
前缀,如CORE::gethostbyname
。
my $host = gethostbyname 'www.baidu.com'; my $host = gethostbyaddr '192.168.100.21';
一样的,这个模块提供了hostent各字段对应的方法和变量:
$host->name ==> $h_name $host->addr ==> $h_addr $host->aliases ==> @h_aliases $host->addrtype ==> $h_addrtype $host->length ==> $h_length $host->addr_list ==> @h_addr_list
此外,还提供了gethost()方法来替代gethostbyname()和gethostbyaddr(),它根据传递的参数类型来决定调用这两个方法中的哪个。
相关函数和模块:
这个用的很少,由于它解析/etc/networks文件,而这个文件是定义网段(注意是网段而不是具体的IP地址)和网段字符串名一一映射关系的。并且,它指定指定A、B、C类网段(注意是网段而不是具体的IP地址),因此这个文件中网段老是以.0
结尾的。
例如,基本上全部操做系统上都有这么两行:
default 0.0.0.0 loopback 127.0.0.0
这表示default表明的是0.0.0.0
这个默认网段,loopback表示的是127.0.0.0
网段。当有须要解析/etc/networks文件的程序(如route命令解析该文件,ip命令不解析该文件)须要用到网段时,就能够指定或显示该网段对应的名称,固然在查询的时候可使用-n
(通常是这个选项),来禁用名称到ID的解析映射。
跳过,基本用不上。
/etc/services中记录了服务和端口/协议相关的信息。例如:
# service-name port/protocol [aliases ...] [# comment] tcpmux 1/tcp # TCP port service multiplexer tcpmux 1/udp # TCP port service multiplexer rje 5/tcp # Remote Job Entry rje 5/udp # Remote Job Entry echo 7/tcp echo 7/udp discard 9/tcp sink null discard 9/udp sink null $ grep ' 22/' /etc/services ssh 22/tcp # The Secure Shell (SSH) Protocol ssh 22/udp # The Secure Shell (SSH) Protocol ssh 22/sctp # SSH $ grep ' 3306/' /etc/services mysql 3306/tcp # MySQL mysql 3306/udp # MySQL
/etc/protocols文件中保存了tcp/ip协议栈各类协议的信息,包括协议名、代号、别名等等。例如ip协议的代号是0,别名是IP,tcp协议的代号是6,别名是TCP。这个文件是不能改的,一改就致使发送的TCP/IP相关的数据包出错。
这两个服务基本上不用管,除非要TCP/IP编程。