使用 Python 獲取 Linux 系統資訊

oschina發表於2013-08-01

  在本文中,我們將會探索使用Python程式語言工具來檢索Linux系統各種資訊。走你。

 哪個Python版本?

  當我提及Python,所指的就是CPython 2(準確的是2.7).我會顯式提醒那些相同的程式碼在CPython 3 (3.3)上是不工作的,以及提供一份解釋不同之處的備選程式碼。請確保你已經安裝了CPython,在終端上輸入python或者python3回車,然後你在終端上應該能看到python的提示符(prompt)。

  請注意,所有的程式在它們第一行都是#!/usr/bin/env/python,也就是說,我們想要Python的直譯器來執行這些指令碼。因此,如果你想你的指令碼具有執行性,請使用chmod +x your-script.py, 那麼你就可以使用./your-script.py來執行它了(在本文中你將會看到這種方式)

 探索platform模組

  platform模組在標準庫中,它有很多執行我們獲得眾多系統資訊的函式。讓我們執行Python直譯器來探索它們中的一些函式,那就從platform.uname()函式開始吧:

>>> 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命令,那麼你就會認出來這個函式就是這個命令的一個介面。在Python 2上,它會返回一個包含系統型別(或者核心版本),主機名,版本,釋出版本,機器的硬體以及處理器資訊元組(tuple)。你可以使用下標訪問個別屬性,像這樣:

>>> platform.uname()[0]
'Linux'

  在Python 3上,這個函式返回的是一個命名元組:

>>> 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()函式返回的有關你所在的linux釋出版本的詳細資訊。例如,在Fedora 18系統上,這個命令會返回如下資訊:

>>> platform.linux_distribution()
('Fedora', '18', 'Spherical Cow')

  這個返回結果中包含了版本釋出名,版本以及代號元組。特定的Python版本支援的釋出版本上可以通過_supported_dists顯示的值獲得。

>>> platform._supported_dists
('SuSE', 'debian', 'fedora', 'redhat', 'centos', 'mandrake',
'mandriva', 'rocks', 'slackware', 'yellowdog', 'gentoo',
'UnitedLinux', 'turbolinux')

  如果你的linux釋出版本不在其中(或者其中之一的衍生髮行版)。那麼你很可能呼叫了上面這個函式而看不到任何有用的資訊。

  platform模組的最後一個函式,我們將會看看architecture()函式。當你無參的呼叫這個函式,它會返回包含架構位數以及python可執行的格式的元組,像這樣:

>>> platform.architecture()
('64bit', 'ELF')

  在32位的系統上,你將會看到:

>>> platform.architecture()
('32bit', 'ELF')

  如果你指定了系統上其他任何可執行的,你都將會獲得相似的結果,如同這樣:

>>> platform.architecture(executable='/usr/bin/ls')
('64bit', 'ELF')

  鼓勵探索platform模組的除了這些的其它函式,找出你現在執行的Python版本。如果你想知道這個模組是如何獲取這些資訊的,你可以深入檢視PYthon原始碼目錄下的Lib/platform.py檔案。

  os和sys模組也可以獲得一些系統屬性,例如原生的位元組序。接下來,我們超越Python標準庫模組,去探索一些在linux系統通過proc和sysfs檔案系統使之訪問資訊成為可能。注意的是通過檔案系統來訪問資訊將會在不同的硬體架構有所不同。所以在讀本文或是寫指令碼時要時刻記住可以試圖從這些檔案獲取資訊。

 CPU資訊

  /proc/cpuinfo檔案包含了你的系統處理器單元的資訊。例如,這裡就是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執行這個程式時,你會在螢幕上看到所有/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

  迄今為止,我們已有兩種方式來找出我們所使用的系統的架構。從技術上講是正確的,兩個方 式實際上報告了你係統執行的核心架構,所以,如果你的電腦是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檔案以及使用簡單文字處理技術就可以獲得我們要查詢的資料是可能的。為了給其他程式更好的使用這些資料,一個更好的主意就是使/proc/cpuinfo的內容成為標準的資料結構,譬如字典(dictionary)。這個注意很簡單:如果你檢視這個檔案的內容,你就會發現對於每個處理器單元,都有好些鍵值對(在先前的例子中,我們列印了每個處理器的模型名,即模型名就是關鍵字)。不同的處理器 單元的資訊可以使用空白行隔開。構造一個字典資料結構包含每個處理器單元的關鍵字是很簡單的。對於每個關鍵字,對於處理器單元的值都在/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(有序字典)而不是常規的字典,能夠使用鍵值有序的儲存在檔案裡。所以,第一個處理器單元的資料之後就是第二個處理器單元的資料,以此類推。你可以使用過濾器來過濾你所查詢的資訊(如同在if __name__ == '__main__'塊中演示的那樣)。上面的程式每次執行後都會列印每個處理器單元的模型名(如通過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包含了你電腦的主存的資訊。下面的這個程式建立了一個使用這個檔案的內容填充的字典。

#!/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__塊中有所表示)。當你執行這個程式,你該會看到像下面類似的輸出:

Total memory: 7897012 kB
Free memory: 249508 kB

 網路統計資訊

  接下來,我們會探索我們電腦系統的網路裝置。我們將會獲得系統的網路介面,以及當系統重啟之後通過它們資料傳送和接受資料的資訊。 /proc/net/dev檔案讓這些資訊可用。如果你檢查了這個檔案的內容,你就會注意到頭一兩行包含了頭資訊等等,這個檔案第一列是網路介面名,第二和第三列顯示了接收和傳送的位元組數資訊(例如總髮送位元組數,包數,錯誤等等)。這裡我們所感興趣的就是他哦難過不同的網路裝置提取出總髮送資料和接收資料。下面的程式碼展示了怎麼從/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)) 

  當你執行上面的程式,下面這個輸出就會顯示從你最近重啟之後網路裝置總接收和傳送的資料,單位為兆。

em1: 0.0 MiB 0.0 MiB
wlan0: 2651.40951061 MiB 183.173976898 MiB

  你可以使用持久的資料儲存機制來連線,來寫出自己的資料使用監控程式。

 程式資訊

  /proc目錄包含了所有正執行的程式目錄。這些目錄的名字和程式的識別符號是一樣的。所以,如果你遍歷/proc目錄下那些使用數字作為它們的名字的目錄,你就會獲得所有現在正在執行的程式列表。在下面的程式碼中process_list()函式返回所有現在正在執行的程式的識別符號列表。當你執行這個程式後,這個列表的長度就是在系統上執行的總程式數。

#!/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)))

  上面的程式當執行後會顯示和下面類似的輸出:

Total number of running processes:: 229

  每個程式目錄包含了一些其他檔案和目錄,如程式命令的呼叫,它正使用的共享庫以及其它的。

 塊裝置

  下一個程式通過讀sysfs虛擬檔案系統列出所有塊裝置。你係統中的塊裝置可以從/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沒有了命令列就是個渣。],它允許人麼指定命令列引數來定製程式的預設行為。argparse模組就提供了和linux命令列實用工具類似的介面。下面的程式碼展示了程式如何獲得系統上的所有使用者以及列印它們的登入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()

  當執行這個程式之後,它會列印系統上所有的使用者以及他們登入shell名。

  現在,你想要程式的使用者能夠選擇是否想看系統使用者(像daemon, apache)。我們擴充套件前面的程式碼,第一次使用argparse模組來實現這個特性,如下。

#!/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選項執行上面的程式,你會看到友好的幫助資訊:可選項以及它們的作用。

$ ./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

  當你傳入一個非法的引數,這個程式就會發牢騷(報錯)

$ ./getusers.py --param
usage: getusers.py [-h] [--no-system]
getusers.py: error: unrecognized arguments: --param

  在上面的程式中,我們簡單的理解了如何使用argparse模組。parser = argparse.ArgumentParser(description="User/Password Utility")語句建立了一個帶說明程式是做什麼的可選描述的ArgumentParser物件,

  然後,我們新增引數。我們想要程式能夠識別接下來這條語句 add_argument()。parser.add_argument('--no-system', action='store_true', dest='no_system', default = False, help='Specify to omit system users')。第一個方法的引數是當系統呼叫這個程式,程式使用著將要提供這個引數的名稱,接下來的引數acton=store_true表明它是一個布林選擇。那就是說,它真或假影響程式的某些行為。dest為可定製化引數,它的值可以提供給程式使用。假如這個值使用者不提供,這個值預設false。最後的引數程式顯示的幫助資訊。最後,引數被解析通過args=parser.parse_args()方法。一旦解析方法被做,使用者選項的值能夠被抓取到通過相應的語法引數option_dest,當你配置引數的時候,option_dest是一個你指定的目的變數。getusers(args.no_system)這條語句使用使用者提供引數的值將會回撥getusers()方法。

  下面的程式展示瞭如何指定非布林型別的選項。該程式是對第6個程式的重寫,附加了一個選項用於指定你感興趣的網路裝置。

#!/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))

  當你不帶任何引數執行程式的時候,程式的行為與之前的版本完全一致。然後,你也可以指定感興趣的網路裝置。例如:

$ ./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

 指令碼的系統範圍可用性

  在本文的幫助下,你可能已經可以寫一個或多個有用的指令碼,就像其它linux命令一樣,你想要每天都使用它們。最簡單的方式是將指令碼設定為可執行的,然後為指令碼設定一個BASH別名。你也可以移除.py副檔名,然後將指令碼放在諸如/usr/local/sbin這樣的標準位置。

 其它有用的標準庫模組

  除了本文中已經提到的標準庫模組,還有很多其它有用的標準模組:subprocess、ConfigParser、readline和curses。

 接下來做什麼?

  在這個階段,依靠你自己使用Python的經驗,探索Linux內部,你可以參考下面的任一方式。如果你曾經需要寫很多shell指令碼/命令流水線來探索Linux內部,那麼試一下Python。如果你想要一個更簡單的方式來編寫執行很多工的實用程式指令碼,那麼試一下Python。最後,如果你已經使用Python在Linux上別寫其它目的的程式,那麼試一下用Python探索Linux內部。

 資源

  Python資源

  系統資訊

  原文地址:http://amitsaha.github.io/site/notes/articles/python_linux/article.html

相關文章