使用Python獲取Linux系統(tǒng)的各種信息

字號:


    這篇文章主要介紹了使用Python獲取Linux系統(tǒng)的各種信息,例如系統(tǒng)類型、CPU信息、內存信息、塊設備等,需要的朋友可以參考下。
    在本文中,我們將會探索使用Python編程語言工具來檢索Linux系統(tǒng)各種信息。走你。
    哪個Python版本?
    當我提及Python,所指的就是CPython 2(準確的是2.7).我會顯式提醒那些相同的代碼在CPython 3 (3.3)上是不工作的,以及提供一份解釋不同之處的備選代碼。請確保你已經安裝了CPython,在終端上輸入python或者python3回車,然后你在終端上應該能看到python的提示符(prompt)。
    請注意,所有的程序在它們第一行都是#!/usr/bin/env/python,也就是說,我們想要Python的解釋器來執(zhí)行這些腳本。因此,如果你想你的腳本具有執(zhí)行性,請使用chmod +x your-script.py, 那么你就可以使用./your-script.py來執(zhí)行它了(在本文中你將會看到這種方式)
    探索platform模塊
    platform模塊在標準庫中,它有很多運行我們獲得眾多系統(tǒng)信息的函數(shù)。讓我們運行Python解釋器來探索它們中的一些函數(shù),那就從platform.uname()函數(shù)開始吧:
    代碼如下:
    >>> import platform
    >>> platform.uname()
    ('Linux', 'fedora.echorand', '3.7.4-204.fc18.x86_64', '#1 SMP Wed Jan 23 16:44:29 UTC 2013', 'x86_64')
    如果你已知道linux上的uname命令,那么你就會認出來這個函數(shù)就是這個命令的一個接口。在Python 2上,它會返回一個包含系統(tǒng)類型(或者內核版本),主機名,版本,發(fā)布版本,機器的硬件以及處理器信息元組(tuple)。你可以使用下標訪問個別屬性,像這樣:
    代碼如下:
    >>> platform.uname()[0]
    'Linux'
    在Python 3上,這個函數(shù)返回的是一個命名元組:
    代碼如下:
    >>> platform.uname()
    uname_result(system='Linux', node='fedora.echorand',
    release='3.7.4-204.fc18.x86_64', version='#1 SMP Wed Jan 23 16:44:29
    UTC 2013', machine='x86_64', processor='x86_64')
    因為返回結果是一個命名元組,這就可以簡單地通過名字來指定特定的屬性,而不是必須記住下標,像這樣:
    代碼如下:
    >>> platform.uname().system
    'Linux'
    platform模塊還有一些上面屬性的直接接口,像這樣:
    代碼如下:
    >>> platform.system()
    'Linux'
    >>> platform.release()
    '3.7.4-204.fc18.x86_64'
    linux_distribution()函數(shù)返回的有關你所在的linux發(fā)布版本的詳細信息。例如,在Fedora 18系統(tǒng)上,這個命令會返回如下信息:
    代碼如下:
    >>> platform.linux_distribution()
    ('Fedora', '18', 'Spherical Cow')
    這個返回結果中包含了版本發(fā)布名,版本以及代號元組。特定的Python版本支持的發(fā)布版本上可以通過_supported_dists顯示的值獲得。
    代碼如下:
    >>> platform._supported_dists
    ('SuSE', 'debian', 'fedora', 'redhat', 'centos', 'mandrake',
    'mandriva', 'rocks', 'slackware', 'yellowdog', 'gentoo',
    'UnitedLinux', 'turbolinux')
    如果你的linux發(fā)布版本不在其中(或者其中之一的衍生發(fā)行版)。那么你很可能調用了上面這個函數(shù)而看不到任何有用的信息。
    platform模塊的最后一個函數(shù),我們將會看看architecture()函數(shù)。當你無參的調用這個函數(shù),它會返回包含架構位數(shù)以及python可執(zhí)行的格式的元組,像這樣:
    代碼如下:
    >>> platform.architecture()
    ('64bit', 'ELF')
    在32位的系統(tǒng)上,你將會看到:
    代碼如下:
    >>> platform.architecture()
    ('32bit', 'ELF')
    如果你指定了系統(tǒng)上其他任何可執(zhí)行的,你都將會獲得相似的結果,如同這樣:
    代碼如下:
    >>> platform.architecture(executable='/usr/bin/ls')
    ('64bit', 'ELF')
    鼓勵探索platform模塊的除了這些的其它函數(shù),找出你現(xiàn)在運行的Python版本。如果你想知道這個模塊是如何獲取這些信息的,你可以深入查看PYthon源碼目錄下的Lib/platform.py文件。
    os和sys模塊也可以獲得一些系統(tǒng)屬性,例如原生的字節(jié)序。接下來,我們超越Python標準庫模塊,去探索一些在linux系統(tǒng)通過proc和sysfs文件系統(tǒng)使之訪問信息成為可能。注意的是通過文件系統(tǒng)來訪問信息將會在不同的硬件架構有所不同。所以在讀本文或是寫腳本時要時刻記住可以試圖從這些文件獲取信息。
    獲取CPU信息
    /proc/cpuinfo文件包含了你的系統(tǒng)處理器單元的信息。例如,這里就是python版的linux命令cat /proc/cpuinfo所做的事:
    代碼如下:
    #! /usr/bin/env python
    """ print out the /proc/cpuinfo
        file
    """
    from __future__ import print_function
    with open('/proc/cpuinfo') as f:
        for line in f:
            print(line.rstrip('\n'))
    當你使用Python 2 或者 Python 3執(zhí)行這個程序時,你會在屏幕上看到所有/proc/cpuinfo的內容(在上面的程序里,rstrip()方法用來刪除每行末尾的換行符)
    在下面的代碼里列舉了使用startwith()字符串方法來顯示你的處理器單元的模式。
    代碼如下:
    #! /usr/bin/env python
    """ Print the model of your
        processing units
    """
    from __future__ import print_function
    with open('/proc/cpuinfo') as f:
        for line in f:
            # Ignore the blank line separating the information between
            # details about two processing units
            if line.strip():
                if line.rstrip('\n').startswith('model name'):
                    model_name = line.rstrip('\n').split(':')[1]
                    print(model_name)
    當你運行這個程序后,你應該會看到你的每個處理器單元的模式名。例如,這里就是在我電腦上所看到的。
    代碼如下:
    Intel(R) Core(TM) i7-3520M CPU @ 2.90GHz
    Intel(R) Core(TM) i7-3520M CPU @ 2.90GHz
    Intel(R) Core(TM) i7-3520M CPU @ 2.90GHz
    Intel(R) Core(TM) i7-3520M CPU @ 2.90GHz
    迄今為止,我們已有兩種方式來找出我們所使用的系統(tǒng)的架構。從技術上講是正確的,兩個方式實際上報告了你系統(tǒng)運行的內核架構,所以,如果你的電腦是64位的,但是運行的是32位的內核,然后上面的方法還是將會顯示為32位的架構。你可以通過從/proc/cpuinfo所列舉的標志中查找lm標志,來找到你的電腦的真實的架構。lm標志代表了長模式,只有64位架構的才會顯示它。下面的程序將會指導你怎樣做:
    代碼如下:
    #! /usr/bin/env python
    """ Find the real bit architecture
    """
    from __future__ import print_function
    with open('/proc/cpuinfo') as f:
        for line in f:
            # Ignore the blank line separating the information between
            # details about two processing units
            if line.strip():
                if line.rstrip('\n').startswith('flags') \
                        or line.rstrip('\n').startswith('Features'):
                    if 'lm' in line.rstrip('\n').split():
                        print('64-bit')
                    else:
                        print('32-bit')
    如我們所看到那樣,讀取/proc/cpuinfo文件以及使用簡單文本處理技術就可以獲得我們要查找的數(shù)據(jù)是可能的。為了給其他程序更好的使用這些數(shù)據(jù),一個更好的主意就是使/proc/cpuinfo的內容成為標準的數(shù)據(jù)結構,譬如字典(dictionary)。這個注意很簡單:如果你查看這個文件的內容,你就會發(fā)現(xiàn)對于每個處理器單元,都有好些鍵值對(在先前的例子中,我們打印了每個處理器的模型名,即模型名就是關鍵字)。不同的處理器單元的信息可以使用空白行隔開。構造一個字典數(shù)據(jù)結構包含每個處理器單元的關鍵字是很簡單的。對于每個關鍵字,對于處理器單元的值都在/proc/cpuinfo文件中。下面的代碼將會指導你怎么做。
    代碼如下:
    #!/usr/bin/env/ python
    """
    /proc/cpuinfo as a Python dict
    """
    from __future__ import print_function
    from collections import OrderedDict
    import pprint
    def cpuinfo():
        ''' Return the information in /proc/cpuinfo
        as a dictionary in the following format:
        cpu_info['proc0']={...}
        cpu_info['proc1']={...}
        '''
        cpuinfo=OrderedDict()
        procinfo=OrderedDict()
        nprocs = 0
        with open('/proc/cpuinfo') as f:
            for line in f:
                if not line.strip():
                    # end of one processor
                    cpuinfo['proc%s' % nprocs] = procinfo
                    nprocs=nprocs+1
                    # Reset
                    procinfo=OrderedDict()
                else:
                    if len(line.split(':')) == 2:
                        procinfo[line.split(':')[0].strip()] = line.split(':')[1].strip()
                    else:
                        procinfo[line.split(':')[0].strip()] = ''
               
        return cpuinfo
    if __name__=='__main__':
        cpuinfo = cpuinfo()
        for processor in cpuinfo.keys():
            print(cpuinfo[processor]['model name'])
    這段代碼中使用了OrderedDict(有序字典)而不是常規(guī)的字典,能夠使用鍵值有序的存儲在文件里。所以,第一個處理器單元的數(shù)據(jù)之后就是第二個處理器單元的數(shù)據(jù),以此類推。你可以使用過濾器來過濾你所查找的信息(如同在if __name__ == '__main__'塊中演示的那樣)。上面的程序每次執(zhí)行后都會打印每個處理器單元的模型名(如通過cpuinfo[processor]['model name']語句表明的那樣)
    代碼如下:
    Intel(R) Core(TM) i7-3520M CPU @ 2.90GHz
    Intel(R) Core(TM) i7-3520M CPU @ 2.90GHz
    Intel(R) Core(TM) i7-3520M CPU @ 2.90GHz
    Intel(R) Core(TM) i7-3520M CPU @ 2.90GHz
    獲取內存信息
    和/proc/cpuinfo相似,文件/proc/meminfo包含了你電腦的主存的信息。下面的這個程序創(chuàng)建了一個使用這個文件的內容填充的字典。
    代碼如下:
    #!/usr/bin/env python
    from __future__ import print_function
    from collections import OrderedDict
    def meminfo():
        ''' Return the information in /proc/meminfo
        as a dictionary '''
        meminfo=OrderedDict()
        with open('/proc/meminfo') as f:
            for line in f:
                meminfo[line.split(':')[0]] = line.split(':')[1].strip()
        return meminfo
    if __name__=='__main__':
        #print(meminfo())
       
        meminfo = meminfo()
        print('Total memory: {0}'.format(meminfo['MemTotal']))
        print('Free memory: {0}'.format(meminfo['MemFree']))
    像先前的,通過它的關鍵字,你可以訪問任何你查詢的指定信息(在if __name__==__main__塊中有所表示)。當你執(zhí)行這個程序,你該會看到像下面類似的輸出:
    代碼如下:
    Total memory: 7897012 kB
    Free memory: 249508 kB
    網(wǎng)絡統(tǒng)計信息
    接下來,我們會探索我們電腦系統(tǒng)的網(wǎng)絡設備。我們將會獲得系統(tǒng)的網(wǎng)絡接口,以及當系統(tǒng)重啟之后通過它們數(shù)據(jù)發(fā)送和接受數(shù)據(jù)的信息。 /proc/net/dev文件讓這些信息可用。如果你檢查了這個文件的內容,你就會注意到頭一兩行包含了頭信息等等,這個文件第一列是網(wǎng)絡接口名,第二和第三列顯示了接收和發(fā)送的字節(jié)數(shù)信息(例如總發(fā)送字節(jié)數(shù),包數(shù),錯誤等等)。這里我們所感興趣的就是他哦難過不同的網(wǎng)絡設備提取出總發(fā)送數(shù)據(jù)和接收數(shù)據(jù)。下面的代碼展示了怎么從/proc/net/dev文件中提取出這些信息。
    代碼如下:
    #!/usr/bin/env python
    from __future__ import print_function
    from collections import namedtuple
    def netdevs():
        ''' RX and TX bytes for each of the network devices '''
        with open('/proc/net/dev') as f:
            net_dump = f.readlines()
       
        device_data={}
        data = namedtuple('data',['rx','tx'])
        for line in net_dump[2:]:
            line = line.split(':')
            if line[0].strip() != 'lo':
                device_data[line[0].strip()] = data(float(line[1].split()[0])/(1024.0*1024.0),
                                                    float(line[1].split()[8])/(1024.0*1024.0))
       
        return device_data
    if __name__=='__main__':
       
        netdevs = netdevs()
        for dev in netdevs.keys():
            print('{0}: {1} MiB {2} MiB'.format(dev, netdevs[dev].rx, netdevs[dev].tx))
    當你運行上面的程序,下面這個輸出就會顯示從你最近重啟之后網(wǎng)絡設備總接收和發(fā)送的數(shù)據(jù),單位為兆。
    代碼如下:
    em1: 0.0 MiB 0.0 MiB
    wlan0: 2651.40951061 MiB 183.173976898 MiB
    你可以使用持久的數(shù)據(jù)存儲機制來連接,來寫出自己的數(shù)據(jù)使用監(jiān)控程序。
    進程信息
    /proc目錄包含了所有正運行的進程目錄。這些目錄的名字和進程的標識符是一樣的。所以,如果你遍歷/proc目錄下那些使用數(shù)字作為它們的名字的目錄,你就會獲得所有現(xiàn)在正在運行的進程列表。在下面的代碼中process_list()函數(shù)返回所有現(xiàn)在正在運行的進程的標識符列表。當你執(zhí)行這個程序后,這個列表的長度就是在系統(tǒng)上運行的總進程數(shù)。
    代碼如下:
    #!/usr/bin/env python
    """
     List of all process IDs currently active
    """
    from __future__ import print_function
    import os
    def process_list():
        pids = []
        for subdir in os.listdir('/proc'):
            if subdir.isdigit():
                pids.append(subdir)
        return pids
    if __name__=='__main__':
        pids = process_list()
        print('Total number of running processes:: {0}'.format(len(pids)))
    上面的程序當執(zhí)行后會顯示和下面類似的輸出:
    代碼如下:
    Total number of running processes:: 229
    每個進程目錄包含了一些其他文件和目錄,如進程命令的調用,它正使用的共享庫以及其它的。
    塊設備
    下一個程序通過讀sysfs虛擬文件系統(tǒng)列出所有塊設備。你系統(tǒng)中的塊設備可以從/sys/block目錄中找到。因此可能會有/sys/block/sda、/sys/block/sdb等這樣的目錄。為了獲取所有這些設備,我們使用正則表達式對/sys/block目錄進行掃描提取感興趣的塊設備。
    代碼如下:
    #!/usr/bin/env python
    """
    Read block device data from sysfs
    """
    from __future__ import print_function
    import glob
    import re
    import os
    # Add any other device pattern to read from
    dev_pattern = ['sd.*','mmcblk*']
    def size(device):
        nr_sectors = open(device+'/size').read().rstrip('\n')
        sect_size = open(device+'/queue/hw_sector_size').read().rstrip('\n')
        # The sect_size is in bytes, so we convert it to GiB and then send it back
        return (float(nr_sectors)*float(sect_size))/(1024.0*1024.0*1024.0)
    def detect_devs():
        for device in glob.glob('/sys/block/*'):
            for pattern in dev_pattern:
                if re.compile(pattern).match(os.path.basename(device)):
                    print('Device:: {0}, Size:: {1} GiB'.format(device, size(device)))
    if __name__=='__main__':
        detect_devs()
    如果你運行該程序,你將會看到下述類似的輸出:
    代碼如下:
    Device:: /sys/block/sda, Size:: 465.761741638 GiB
    Device:: /sys/block/mmcblk0, Size:: 3.70703125 GiB
    當我運行該程序的時候,有個SD內存卡插在電腦上,因此你會看到程序檢測到了它。你也可以擴展該程序識別其它塊設備(比如虛擬硬盤)。
    建立命令行實用工具
    linux中命令行使用工具是無所不在的[@Lesus 注:曾有人說過:linux沒有了命令行就是個渣。],它允許人么指定命令行參數(shù)來定制程序的默認行為。argparse模塊就提供了和linux命令行實用工具類似的接口。下面的代碼展示了程序如何獲得系統(tǒng)上的所有用戶以及打印它們的登錄shell(使用了pwd標準庫模塊):
    代碼如下:
    #!/usr/bin/env python
    """
    Print all the users and their login shells
    """
    from __future__ import print_function
    import pwd
    # Get the users from /etc/passwd
    def getusers():
        users = pwd.getpwall()
        for user in users:
            print('{0}:{1}'.format(user.pw_name, user.pw_shell))
    if __name__=='__main__':
        getusers()
    當運行這個程序之后,它會打印系統(tǒng)上所有的用戶以及他們登錄shell名。
    現(xiàn)在,你想要程序的用戶能夠選擇是否想看系統(tǒng)用戶(像daemon, apache)。我們擴展前面的代碼,第一次使用argparse模塊來實現(xiàn)這個特性,如下。
    代碼如下:
    #!/usr/bin/env python
    """
    Utility to play around with users and passwords on a Linux system
    """
    from __future__ import print_function
    import pwd
    import argparse
    import os
    def read_login_defs():
        uid_min = None
        uid_max = None
        if os.path.exists('/etc/login.defs'):
            with open('/etc/login.defs') as f:
                login_data = f.readlines()
               
            for line in login_data:
                if line.startswith('UID_MIN'):
                    uid_min = int(line.split()[1].strip())
               
                if line.startswith('UID_MAX'):
                    uid_max = int(line.split()[1].strip())
        return uid_min, uid_max
    # Get the users from /etc/passwd
    def getusers(no_system=False):
        uid_min, uid_max = read_login_defs()
        if uid_min is None:
            uid_min = 1000
        if uid_max is None:
            uid_max = 60000
        users = pwd.getpwall()
        for user in users:
            if no_system:
                if user.pw_uid >= uid_min and user.pw_uid <= uid_max:
                    print('{0}:{1}'.format(user.pw_name, user.pw_shell))
            else:
                print('{0}:{1}'.format(user.pw_name, user.pw_shell))
    if __name__=='__main__':
        parser = argparse.ArgumentParser(description='User/Password Utility')
        parser.add_argument('--no-system', action='store_true',dest='no_system',
                            default = False, help='Specify to omit system users')
        args = parser.parse_args()
        getusers(args.no_system)
    使用--help選項執(zhí)行上面的程序,你會看到友好的幫助信息:可選項以及它們的作用。
    代碼如下:
    $ ./getusers.py --help
    usage: getusers.py [-h] [--no-system]
    User/Password Utility
    optional arguments:
      -h, --help   show this help message and exit
      --no-system  Specify to omit system users
    上面程序使用的一個例子,如下所示:
    代碼如下:
    $ ./getusers.py --no-system
    gene:/bin/bash
    當你傳入一個非法的參數(shù),這個程序就會發(fā)牢騷(報錯)
    代碼如下:
    $ ./getusers.py --param
    usage: getusers.py [-h] [--no-system]
    getusers.py: error: unrecognized arguments: --param
    在上面的程序中,我們簡單的理解了如何使用argparse模塊。parser = argparse.ArgumentParser(description="User/Password Utility")語句創(chuàng)建了一個帶說明程序是做什么的可選描述的ArgumentParser對象,
    然后,我們添加參數(shù)。我們想要程序能夠識別接下來這條語句 add_argument()。
    parser.add_argument('--no-system', action='store_true', dest='no_system', default = False, help='Specify to omit system users')。第一個方法的參數(shù)是當系統(tǒng)調用這個程序,程序使用著將要提供這個參數(shù)的名稱,接下來的參數(shù)acton=store_true表明它是一個布爾選擇。那就是說,它真或假影響程序的某些行為。dest為可定制化參數(shù),它的值可以提供給程序使用。假如這個值用戶不提供,這個值默認false。最后的參數(shù)程序顯示的幫助信息。最后,參數(shù)被解析通過args=parser.parse_args()方法。一旦解析方法被做,用戶選項的值能夠被抓取到通過相應的語法參數(shù)option_dest,當你配置參數(shù)的時候,option_dest是一個你指定的目的變量。getusers(args.no_system)這條語句使用用戶提供參數(shù)的值將會回調getusers()方法。
    下面的程序展示了如何指定非布爾類型的選項。該程序是對第6個程序的重寫,附加了一個選項用于指定你感興趣的網(wǎng)絡設備。
    代碼如下:
    #!/usr/bin/env python
    from __future__ import print_function
    from collections import namedtuple
    import argparse
    def netdevs(iface=None):
        ''' RX and TX bytes for each of the network devices '''
        with open('/proc/net/dev') as f:
            net_dump = f.readlines()
       
        device_data={}
        data = namedtuple('data',['rx','tx'])
        for line in net_dump[2:]:
            line = line.split(':')
            if not iface:
                if line[0].strip() != 'lo':
                    device_data[line[0].strip()] = data(float(line[1].split()[0])/(1024.0*1024.0),
                                                        float(line[1].split()[8])/(1024.0*1024.0))
            else:
                if line[0].strip() == iface:
                    device_data[line[0].strip()] = data(float(line[1].split()[0])/(1024.0*1024.0),
                                                        float(line[1].split()[8])/(1024.0*1024.0))   
        return device_data
    if __name__=='__main__':
        parser = argparse.ArgumentParser(description='Network Interface Usage Monitor')
        parser.add_argument('-i','--interface', dest='iface',
                            help='Network interface')
        args = parser.parse_args()
        netdevs = netdevs(iface = args.iface)
        for dev in netdevs.keys():
            print('{0}: {1} MiB {2} MiB'.format(dev, netdevs[dev].rx, netdevs[dev].tx))
    當你不帶任何參數(shù)執(zhí)行程序的時候,程序的行為與之前的版本完全一致。然后,你也可以指定感興趣的網(wǎng)絡設備。例如:
    代碼如下:
    $ ./net_devs_2.py
    em1: 0.0 MiB 0.0 MiB
    wlan0: 146.099492073 MiB 12.9737148285 MiB
    virbr1: 0.0 MiB 0.0 MiB
    virbr1-nic: 0.0 MiB 0.0 MiB
    $ ./net_devs_2.py  --help
    usage: net_devs_2.py [-h] [-i IFACE]
    Network Interface Usage Monitor
    optional arguments:
      -h, --help            show this help message and exit
      -i IFACE, --interface IFACE
                            Network interface
    $ ./net_devs_2.py  -i wlan0
    wlan0: 146.100307465 MiB 12.9777050018 MiB
    腳本的系統(tǒng)范圍可用性
    在本文的幫助下,你可能已經可以寫一個或多個有用的腳本,就像其它linux命令一樣,你想要每天都使用它們。最簡單的方式是將腳本設置為可執(zhí)行的,然后為腳本設置一個BASH別名。你也可以移除.py擴展名,然后將腳本放在諸如/usr/local/sbin這樣的標準位置。
    其它有用的標準庫模組
    除了本文中已經提到的標準庫模組,還有很多其它有用的標準模組:subprocess、ConfigParser、readline和curses。
    接下來做什么?
    在這個階段,依靠你自己使用Python的經驗,探索Linux內部,你可以參考下面的任一方式。如果你曾經需要寫很多shell腳本/命令流水線來探索Linux內部,那么試一下Python。如果你想要一個更簡單的方式來編寫執(zhí)行很多任務的實用程序腳本,那么試一下Python。最后,如果你已經使用Python在Linux上別寫其它目的的程序,那么試一下用Python探索Linux內部。