Perl獲取主機名、用戶、組、網絡信息

獲取主機名、用戶、組、網絡信息相關函數

首先是獲取主機名的方式,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

  • getXXX:獲取信息操做
  • setXXX:設置操做
  • endXXX:關閉操做

getXXXXent表示從對應文件中讀取每一行並迭代,這個過程當中間會隱式打開對應文件(因此隱式地維護了一個文件句柄)並使用指針指向每次讀取完的位置,可使用setXXXent來重置指針使其指向文件開頭(也就是說從頭開始迭代),使用endXXXent來關閉隱式打開的文件句柄(有些函數會經過網絡解析獲得結果,如DNS解析,endXXXent也會關閉這個網絡鏈接從而終止解析過程)。編程

這裏共有18個函數。包括如下幾個文件(以Linux操做系統爲例):數組

  • /etc/group:{end | set | get}grent
  • /etc/passwd、/etc/shadow:{end | set | get}pwent
  • /etc/hosts:{end | set | get}hostent
  • /etc/networks:{end | set | get}netent
  • /etc/services:{end | set | get}servent
  • /etc/protocols:{end | set | get}protoent

經過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

  • getgrgid:根據給定的gid,搜索/etc/group中對應的組,並返回該組信息
  • getgrnam:根據給定的組name,搜索/etc/group中對應的組,並返回該組信息
  • getpwnam:根據給定的uid,搜索/etc/passwd、/etc/shadow中對應的用戶,並返回該用戶信息
  • getpwuid:根據給定的用戶name,搜索/etc/passwd、/etc/shadow中對應的用戶,並返回該用戶信息
  • getlogin:返回當前登陸的用戶名,返回的依據是從/run/utmp或/etc/utmp搜索

獲取網絡信息的操做還有:ssh

  • gethostbyaddr:經過ipv4地址獲取host信息(這裏的host和dns解析有關,即對應於/etc/hosts)
  • gethostbyname:經過主機名獲取host信息(這裏的host和dns解析有關,即對應於/etc/hosts)
  • getnetbyaddr:獲取網段信息(對應於/etc/networks文件)
  • getnetbyname:獲取網段信息(對應於/etc/networks文件)
  • getservbyname:獲取服務信息(對應於/etc/services文件)
  • getservbyport:獲取服務信息(對應於/etc/services文件)
  • getprotobyname:獲取協議信息(對應於/etc/protocols文件)
  • getprotobynumber:獲取協議信息(對應於/etc/protocols文件)

這些函數在標量上下文中返回單個name或id類數據,在列表上下文中返回各自的ent結構體對象。

雖然看着一大堆,分類後其實很容易記憶。可是,這些函數都不是很方便,由於有很多函數裏的參數或返回值是須要或通過了二進制打包的。好比gethostbyaddr函數的參數是一個ip地址,但不能直接傳遞"192.168.100.12"這樣的地址,而是先使用pack()或使用Socket模塊提供的inet_aton()將其轉換成二進制格式,再傳遞給gethostbyaddr

正是由於這些函數使用起來並不方便,因此每種類型(passwd、group、host、network、service、protocol)都有對應的面向對象的模塊,使用這些模塊中的方法,能夠免去轉換的過程。它們對應的模塊爲:

  • User::pwent
  • User::grent
  • Net::hostent
  • Net::netent
  • Net::servent
  • Net::protoent

因爲不少函數在用法上是很是相似的,因此在後文介紹重複內容時僅將簡單說明重複函數的用法。

獲取用戶和組信息

相關的函數和模塊有:

  • {get | set | end}pwent
  • {get | set | end}grent
  • getgrgid
  • getgrname
  • getpwuid
  • getpwnam
  • getlogin
  • User::pwent
  • User::grent

group信息

首先解釋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

user信息

獲取用戶信息和組信息的方式是同樣的,相關函數爲getpwentsetpwentendpwentgetpwnamgetpwuid,惟一不同的是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

User::grent和User::pwent模塊

這兩個模塊提供了面向對象方式的操做。導入這兩個模塊,裏面的函數默認會覆蓋同名內置函數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

地址解析:hosts信息

通過前面grent和pwent的解釋以後,再理解hosts、net、serv、proto就簡單多了。

hosts相關函數和模塊:

  • gethostent
  • sethostent
  • endhostent
  • gethostbyaddr
  • gethostbyname
  • Net::hostent

gethostentgethostbyaddrgethostbyname用來給給定的主機名或地址進行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.172183.232.231.174

hostent結構

和前面介紹的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是以二進制方式打包的地址列表,沒法直接輸出,須要將每一個地址元素都解包。解包的方式有兩種:
    • 使用unpack():for (@addrs) { my ($a, $b, $c, $d) = unpack 'C4', $_ }
    • 使用Socket模塊的inet_ntoa():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 $_;
}

一個簡單的DNS A記錄解析程序

在使用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

Net::hostent

User::pwentUser::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(),它根據傳遞的參數類型來決定調用這兩個方法中的哪個。

網段解析:networks信息

相關函數和模塊:

  • getnetent
  • setnetent
  • endnetent
  • getnetbyaddr
  • getnetbyname
  • Net::netent

必要基礎

這個用的很少,由於它解析/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的解析映射。

用法

跳過,基本用不上。

服務和協議:services和protocols

/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編程。

相關文章
相關標籤/搜索