zoukankan      html  css  js  c++  java
  • Perl获取主机名、用户、组、网络信息

    获取主机名、用户、组、网络信息相关函数

    首先是获取主机名的方式,Perl提供了Sys::Hostname模块,可以查询当前的主机名:

    use Sys::Hostname;
    print hostname, "
    ";
    

    Perl中提供了下面一大堆的内置函数用来获取用户、组、网络相关的信息。这些perl函数在C中也都有对应的函数。

    # 获取和设置用户和组
    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类:

    • 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结构体的各个字段。

    这里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
    

    这种结构体对象并非只能从这些文件中获取,自己也可以构建或通过其它函数返回,相关内容在后面介绍中会提到。

    除了上面的分类,用户、组还能继续分为:

    • 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搜索

    获取网络信息的操作还有:

    • 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
    ";
            } else {
                print "$lookup (real name $name) IP address: @addrs
    ";
            }
        } else {
            print "host $lookup not found
    ";
        }
    }
    

    执行:

    $ 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编程。

  • 相关阅读:
    I NEED A OFFER!
    水题 Codeforces Round #303 (Div. 2) A. Toy Cars
    模拟 HDOJ 5099 Comparison of Android versions
    模拟 HDOJ 5095 Linearization of the kernel functions in SVM
    贪心 HDOJ 5090 Game with Pearls
    Kruskal HDOJ 1863 畅通工程
    Kruskal HDOJ 1233 还是畅通工程
    并查集 HDOJ 1232 畅通工程
    DFS/并查集 Codeforces Round #286 (Div. 2) B
    水题 Codeforces Round #286 (Div. 2) A Mr. Kitayuta's Gift
  • 原文地址:https://www.cnblogs.com/f-ck-need-u/p/10486209.html
Copyright © 2011-2022 走看看