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