共计 12166 个字符,预计需要花费 31 分钟才能阅读完成。
在本文中,我们将会探索使用 Python 编程语言工具来检索 Linux 系统各种信息。
当我提及 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 模块在标准库中,它有很多运行我们获得众多系统信息的函数。让我们运行 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)。你可以使用下标访问个别属性,像这样:
0] | platform.uname()[|
'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') |
如果你指定了系统上其他任何可执行的,你都将会获得相似的结果,如同这样:
'/usr/bin/ls') | platform.architecture(executable=|
('64bit', 'ELF') |
鼓励探索 platform 模块的除了这些的其它函数,找出你现在运行的 Python 版本。如果你想知道这个模块是如何获取这些信息的,你可以深入查看 PYthon 源码目录下的 Lib/platform.py 文件。
os 和 sys 模块也可以获得一些系统属性,例如原生的字节序。接下来,我们超越 Python 标准库模块,去探索一些在 linux 系统通过 proc 和 sysfs 文件系统使之访问信息成为可能。注意的是通过文件系统来访问信息将会在不同的硬件架构有所不同。所以在读本文或是写脚本时要时刻记住可以试图从这些文件获取信息。
/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 | |
使用 --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 toomit 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 内部。 | |
阿里云 2 核 2G 服务器 3M 带宽 61 元 1 年,有高配 | |
腾讯云新客低至 82 元 / 年,老客户 99 元 / 年 | |
代金券:在阿里云专用满减优惠券 | |
