用Python製作檢測Linux運行資訊的工具的教程_python

來源:互聯網
上載者:User

在這篇文章裡,我們將會探索如何使用Python語言作為一個工具來檢測Linux系統各種運行資訊。讓我們一起來學習吧。

哪種Python?

當我提到Python時,我一般是指CPython 2(準確來說是2.7)。當同樣的代碼不能在CPython3(3.3)運行時,我們明確地把它指出並給出替代的代碼,解釋它們之間的不同點。請確保你已經安裝了CPython,在終端輸入python或者python3你會看到Python提示符出現在你的終端裡。

請注意,所有的指令碼程式都會以#!/usr/bin/env python作為第一行,意味著我們要Python解析器去運行這些指令碼。因此,如果你使用 chmod +x your-script.py 命令給你的指令碼添加可執行檔許可權,你可以使用./your-script.py命令直接運行你的指令碼(你將會在這篇文章裡看到這種操作)

探索platform模組

在標準庫中的platform模組有大量的函數讓我們去檢查各種系統資訊。我們一起來開啟Python解譯器(譯者:直接在命令列輸入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命令,你會意識到這個函數就是uname命令的一個介面。在Python 2,這個函數會返回一個由系統類別型(或者核心類型),主機名稱,版本號碼,發行版號,主機硬體架構和處理器類型組成的元組。你可以使用索引來擷取單個屬性,像這樣:
 

>>> 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:29UTC 2013', machine='x86_64', processor='x86_64')

因為傳回值是個預設命名的元組,所以我們可以輕易地通過變數名來擷取單個屬性而不用去記住各個屬性的下標,像這樣:
 

>>> platform.uname().system'Linux'

platfrom模組還提供了一些直接的介面來擷取上面的屬性值,像這些:
 

>>> platform.system()'Linux' >>> platform.release()'3.7.4-204.fc18.x86_64'

函數linx_distribution()返回你正在使用的Linux發行版的詳細資料。舉個例子,在Fedora 18系統中,這條命令會返回下面的資訊:
 

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

傳回值是一個由發行版本名,版本號碼,代號組成的元組。你可以通過_supported_dists屬性來列印你所用的Python版本支援哪些發行版本:
 

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

如果你的Linux發行版本不是上面那些的其中一個(或者是其中一個的衍生版),那麼你調用上面的函數時不會看到任何有用的資訊。

最後一個我們要探索的platfrom函數是architecture()函數。當你不添加任何的參數來調用這個函數時,這個函數會返回一個由位架構和Python可執行檔格式組成的元組。比如:
 

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

在32位的Linux系統中,你會看到:
 

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

如果你指定其他任意的系統可執行程式作為參數,你會得到類似的結果:
 

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

我們鼓勵你去探索platfrom模組中的其他函數,讓你找到你當前使用的Python的版本。如果你非常想知道這個模組是怎樣擷取這些資訊的,你可以去看Python原始碼目錄下的Lib/platfrom.py檔案。

os和sys模組同樣是一個擷取統屬性的有用模組就像本地BYTEORDER一樣。下一步,我們將會不使用Python你標準庫模組來探索一些擷取Linux系統資訊的通用方法,這次我們通過proc和sys檔案系統來實現。要注意的是,通過這些檔案系統擷取的資訊在不同的硬體架構裡會有所不同。因此,在閱讀這篇文章和寫指令碼從這些檔案裡擷取系統資訊時要它記住。

CPU資訊

/proc/cpuinfo這個檔案包含了你系統的處理單元資訊。比如,這裡有一個與在命令列輸入cat /proc/cpuinfo 具備同樣功能的Python指令碼
 

#! /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.90GHzIntel(R) Core(TM) i7-3520M CPU @ 2.90GHzIntel(R) Core(TM) i7-3520M CPU @ 2.90GHzIntel(R) Core(TM) i7-3520M CPU @ 2.90GHz

到目前為止我們已經有好幾種方法用來擷取我們的電腦系統構架了。從技術的角度準確地說,所有的這些方法實際上是呈現了你啟動並執行系統核心的架構。因此,如果你電腦實際上是64位的機器,但是運行著32位的核心,那麼上面的方法將會顯現你的電腦是32位架構的。為了找出電腦的正確架構,你可以查看在/proc/cpuinfo中的屬性列表的lm屬性。1m屬性代表著長模式(Long mode)並且只會在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裡擷取的內容轉換為標準的資料機構,比如轉換為字典類型。方法很簡單:如果你看了這個檔案,你會發現對於每一個處理單元都是以索引值對形式存在(在之前的一個例子中,我們列印的處理器機型名時,這裡的model name就是一個鍵。)每個不同處理器單元的資訊都會用空行來分開。這使我們能方便地以每個處理單中繼資料為鍵來構建字典資料結構。這些鍵(key)都有一個值(value),每個值又對應著每一個處理單元在/proc/cupinfo檔案中的所有資訊。下一個代碼清單告訴你怎樣做:

#!/usr/bin/env/ python """/proc/cpuinfo as a Python dict"""from __future__ import print_functionfrom collections import OrderedDictimport 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__'語句塊裡展示的一樣)。當上面的指令碼運行時會再次列印出每個處理單元的model name(通過print(cpuinfo[processor]['model name']語句來展示)
 

Intel(R) Core(TM) i7-3520M CPU @ 2.90GHzIntel(R) Core(TM) i7-3520M CPU @ 2.90GHzIntel(R) Core(TM) i7-3520M CPU @ 2.90GHzIntel(R) Core(TM) i7-3520M CPU @ 2.90GHz

記憶體資訊

與/proc/cpuinfo類似,/proc/meminfo檔案包含了你電腦的主存資訊。下一個指令碼產生一個包含這個檔案內容的字典並把它輸出。

#!/usr/bin/env python from __future__ import print_functionfrom 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 kBFree memory: 249508 kB

網路統計

下面,我們會探索我們電腦系統的網路裝置。我們將會檢索系統的網路介面和系統開啟後發送和接收到的位元組資料。這些資訊可以在/proc/net/dev檔案中擷取。如果你審查過這個檔案的內容,你會發現前兩行包含了頭資訊-i.e.檔案中的第一列是網路介面名,第二和第三列展示了接收和傳輸的位元組資訊(比如,總發送位元組,資料包數量,錯誤統計,等等)。我們感興趣的是如何擷取不同的網路裝置的總資料發送和接受量。下一個代碼清單展示了我們如何從/proc/net/dev提出這些資訊:

#!/usr/bin/env pythonfrom __future__ import print_functionfrom 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))

當你運行上面的指令碼時,會以MiB為單位輸出從你最近的一次重啟後你的網路裝置接受和發送的資料。正如下面展示的:
 

em1: 0.0 MiB 0.0 MiBwlan0: 2651.40951061 MiB 183.173976898 MiB

你可能會利用一個持久性儲存機制和這個指令碼來寫一個你自己的資料使用監控程式。

進程

/proc目錄同樣包含了每個運行進程的目錄。這些目錄的名稱以相應的進程ID來命名。因此,如果你遍曆/proc目錄下的所有以數字命名的目錄,你會得到一個所有當前運行進程的ID列表。下面的代碼清單裡的process_list()函數返回一個包含所有當前運行進程ID的列表。這個列表的長度等於系統運行進程的總數,正如你運行這個指令碼看到的一樣:
 

#!/usr/bin/env python""" List of all process IDs currently active""" from __future__ import print_functionimport osdef 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)))

運行上面的指令碼時,輸出結果和下面輸出類似:

每個進程目錄都包含了大量的其他檔案和目錄,這些目錄包含了各種關於進程調用命令,使用的共用庫和其他的資訊。

塊裝置

接下來的指令碼通過訪問sysfs虛擬檔案系統列出了所有的塊裝置資訊。你能夠在/sys/block目錄下找到系統上的所有塊裝置。因此,你的系統上會有/sys/block/sda,/sys/block/sdb和其他的類似目錄。為了找到這些裝置,我們可以遍曆/sys/block目錄然後通過簡單的Regex去匹配我們要尋找的內容。

#!/usr/bin/env python """Read block device data from sysfs""" from __future__ import print_functionimport globimport reimport os # Add any other device pattern to read fromdev_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 GiBDevice:: /sys/block/mmcblk0, Size:: 3.70703125 GiB

當我運行這個指令碼時,我額外插入了一張SD卡。所以你會看到這個指令碼檢測到了它(上面輸出的第二行,譯者注)。你同樣可以擴充這個指令碼去識別其他的塊裝置(比如虛擬硬碟)。

構建命令列工具

允許使用者指定命令列參數去自訂程式的預設行為是所有Linux命令列工具的一個普遍特徵。argparse模組能使你程式擁有與內建工具介面類似的介面。下一個代碼清單展示了一個擷取你系統上所有使用者並把它們相應的登陸shell列印出來的程式。

#!/usr/bin/env python """Print all the users and their login shells""" from __future__ import print_functionimport pwd # Get the users from /etc/passwddef 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_functionimport pwdimport argparseimport 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/passwddef 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 --helpusage: 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-systemgene:/bin/bash

當你傳遞一個無效的參數時,指令碼會報錯:
 

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

讓我們一起來簡單地瞭解下在上面的指令碼中我們是如何使用argparse模組的parser=argparse.ArgumentParser(description='User/Password Utility')這行代碼使用一個描述指令碼作用的選擇性參數建立了一個新的ArgumentParser對象。

然後,我們在下一行代碼:parser.add_argument(‘–no-system', action='store_true', dest='no_system', default = False, help='Specify to omit system users')裡使用了add_argument()方法添加一些參數,讓指令碼能夠識別命令列選項。這個方法的第一個參數是指令碼使用者在呼叫指令碼時作為參數提供的選項名。下一個參數action=store_true表明了這是一個布爾選項。也就是說這個參數的有無在一定程度上會對程式產生影響。dest參數指定了一個變數來儲存選項值並提供給指令碼使用。如果使用者沒有提供選項,通過參數default=False可以設定預設值為False。最後一個參數是指令碼展示關於這個選項的協助資訊。最後,使用parse_args()方法處理參數:args=parser.parse_args()。一旦處理完畢,使用args.option_dest可以擷取到使用者提供的選項值,這裡的option_dest就是你設定參數時指定的dest變數。這行代碼getusers(args.no_system)使用使用者提供的no_system選項值作為參數調用了getusers()。

接下來的指令碼展示了你該如何在你的指令碼裡提供使用者一個非布爾值的選項。這個指令碼重寫了代碼清單6,添加了額外的選項讓你能夠指定檢測你感興趣的網路裝置。

#!/usr/bin/env pythonfrom __future__ import print_functionfrom collections import namedtupleimport 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 MiBwlan0: 146.099492073 MiB 12.9737148285 MiBvirbr1: 0.0 MiB 0.0 MiBvirbr1-nic: 0.0 MiB 0.0 MiB $ ./net_devs_2.py --helpusage: 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 wlan0wlan0: 146.100307465 MiB 12.9777050018 MiB

使你的指令碼能在任意地方運行

在這篇文章的協助下,你可能已經能夠給自己寫一個或者更多的有用指令碼,這些指令碼你想每天都使用,就像Linux的其他命令一樣。去實現這個願望最簡單的方式就是使這些指令碼能夠運行(給指令碼添加運行許可權,譯者注)並且給這些命令設定BASH別名(BASH alias)。你同樣可以刪除.py尾碼並且把這個檔案放到一個標準位置比如/usr/local/sbin。

其他的有用標準庫模組

除了到目前為止我們在這篇文章裡看到過的標準庫外,這裡還有很多其它可能有用的標準庫:subprocess,ConfigParser, readline 和 curses。

下一步?

在這個階段,根據你自己的Python經驗和深入探索Linux,你選擇以下方式的一種。如果你已經寫了大量的shell指令碼或者命令管道去深入探索各種Linux,請試一下Python。如果你想以一種更簡易的方式去寫你自己的指令碼工具去執行各種任務,請試一下Python。最後,如果你已經使用其他種類的Python在Linux上編程,祝你使用Python深入探索Linux愉快。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.