1.4 一些正则表达式示例
下面看一些Python正则表达式的示例代码,这将使我们更接近实际应用中的程序。如下所示,以POSIX(UNIX风格操作系统,如Linux、Mac OS X等)的who命令的输出为例,该命令将列出所有登录当前系统中的用户信息。
$ who
wesley console Jun 20 20:33
wesley pts/9 Jun 22 01:38 (192.168.0.6)
wesley pts/1 Jun 20 20:33 (:0.0)
wesley pts/2 Jun 20 20:33 (:0.0)
wesley pts/4 Jun 20 20:33 (:0.0)
wesley pts/3 Jun 20 20:33 (:0.0)
wesley pts/5 Jun 20 20:33 (:0.0)
wesley pts/6 Jun 20 20:33 (:0.0)
wesley pts/7 Jun 20 20:33 (:0.0)
wesley pts/8 Jun 20 20:33 (:0.0)
可能我们想要保存一些用户登录信息,诸如登录名、用户登录的终端类型、用户登录的时间和地点。在前面的示例中使用str.split()方法并不高效,因为此处的空白符既不稳定也不一致。另一个问题是在登录时间戳中间的月、日和时间之间有空格,我们可能想要保存这些连续的字段。
读者需要一些方法描述诸如“分割两个或者多个空白符”之类的模式。这通过正则表达式很容易完成。很快,我们可以使用正则表达式模式ss+,该模式的意思是至少拥有两个以上的空白符。
下面创建一个名为rewho.py的程序,该程序读取who命令的输出,然后假定将得到的输出信息存入一个名为whoadat.txt的文件之中。rewho.py脚本最初如下所示:
import re
f = open('whodata.txt', 'r')
for eachLine in f:
print re.split(r'\s\s+', eachLine)
f.close()
上述代码同样使用原始字符串(将字母“r”或者“R”放置在左引号之前),主要目的是为了避免转义特殊字符串字符,如n,该字符并不是特殊的正则表达式模式。对于确实拥有反斜线的正则表达式模式,读者可能希望逐字地处理它们;否则,读者必须在前面加上双斜线来保持它们的安全。
现在将执行who命令,保存输出到whodata.txt文件之中,然后调用rewho.py查看结果。
$ who > whodata.txt
$ rewho.py
['wesley', 'console', 'Jun 20 20:33\012']
['wesley', 'pts/9', 'Jun 22 01:38\011(192.168.0.6)\012']
['wesley', 'pts/1', 'Jun 20 20:33\011(:0.0)\012']
['wesley', 'pts/2', 'Jun 20 20:33\011(:0.0)\012']
['wesley', 'pts/4', 'Jun 20 20:33\011(:0.0)\012']
['wesley', 'pts/3', 'Jun 20 20:33\011(:0.0)\012']
['wesley', 'pts/5', 'Jun 20 20:33\011(:0.0)\012']
['wesley', 'pts/6', 'Jun 20 20:33\011(:0.0)\012']
['wesley', 'pts/7', 'Jun 20 20:33\011(:0.0)\012']
['wesley', 'pts/8', 'Jun 20 20:33\011(:0.0)\012']
这是非常好的一次尝试。首先,我们不期望单个制表符(ASCII 011)作为输出的一部分(可能看起来像是至少两个空白符),然后可能我们并不真的希望保存n(ASCII 012)作为每一行的终止符。我们现在将修复这些问题,然后通过一些改进来提高应用的整体 质量。
首先,应当在脚本内部运行who命令而不是在外部,然后将输出存入whodata.txt文件,如果手动重复做这件事很快就会感到厌倦。要在该程序中调用其他程序,需要调用os.popen()命令。尽管os.popen()命令现在已经被subprocess模块所替换,但它更容易使用,而且此处的重点是展示re.split()的功能。
去除尾部的n(使用str.rstrip()),然后添加单个制表符的检查,用于代替re.split()分隔符。示例1-1展示最终的rewho.py脚本在Python 2中的版本。
示例1-1 分割POSIX的who命令输出(rewho.py)
该脚本调用who命令,然后通过不同类型的空白字符分割输入的数据解析输入。
示例1-2 rewho.py脚本的Python 3版本(rewho3.py)
该rewho.py的Python 3版本仅简单地运用print()函数替换了print语句。当使用with语句(从Python 2.5版本起可用)时,记住,file(Python 2)或者io(Python 3)对象的上下文管理器会自动调用f.close()。
通过使用with语句,拥有上下文管理器的对象变得更易于使用。关于with语句和上下文管理的更多信息,请参考Core Python Pragramming或者Core Pythom Language Fundamentals中的“Errors and Exceptions”章节。记住,两个版本(rewho.py或者rewho3.py)中的who命令仅能在POSIX系统中使用,除非可以在Windows系统的计算机中使用Cygwin。对于运行Microsoft Windows的个人电脑,可以尝试tasklist命令,但读者还需要做一个额外的调整。继续阅读本章后续的章节,查看一个执行that命令的示例。
示例1-3将rewho.py和rewho3.py合并为rewhoU.py,该名称的含义是“通用的rewho”。该程序能够在Python 2和3的解释器下运行。我们欺骗并避免使用print或者print(),方法是使用一个在2.x和3.x版本中都存在并且功能并不齐全的函数:distutils.log.warn()。这是一个单字符串输出函数,因此如果输出要复杂一些,就需要合并所有输出到一个字符串中,然后调用。要在该脚本中指明它的使用方式,就将它命名为printf()。
我们也在此使用with语句。这就意味着读者需要至少使用Python 2.6版本来运行该程序。这还不确切。之前提到过,在2.5版本中with语句是试验性的。这就意味着如果想要在Python 2.5中使用,就需要导入额外的语句:from __future__ import with_statement。如果读者仍在使用2.4或者更老的版本,就不能使用这个import语句,并且必须按照示例1-1那样运行这段代码。
示例1-3 rewho.py脚本的通用版本(rewhoU.py)
该脚本运行在Python 2 和 3 下,通过一个很简单的替换来代替print语句和print()函数。该脚本还包含从Python 2.5开始引入的with语句。
rewhoU.py的创建是一个介绍如何创建通用脚本的示例,这将帮助我们避免为Python 2和3同时维护两个版本的相同脚本。
使用合适的解释器执行这些脚本中的任何一个都会得到正确、简洁的输出。
$ rewho.py
['wesley', 'console', 'Feb 22 14:12']
['wesley', 'ttys000', 'Feb 22 14:18']
['wesley', 'ttys001', 'Feb 22 14:49']
['wesley', 'ttys002', 'Feb 25 00:13', '(192.168.0.20)']
['wesley', 'ttys003', 'Feb 24 23:49', '(192.168.0.20)']
同样不要忘记,之前的小节介绍过re.split()函数也可以使用可选的flage参数。
在Windows计算机上可以使用tasklist命令替代who来得到类似的结果。让我们查看该命令的输出结果。
C:\WINDOWS\system32>tasklist
Image Name PID Session Name Session# Mem Usage
========================= ====== ================ ======== ============
System Idle Process 0 Console 0 28 K
System 4 Console 0 240 K
smss.exe 708 Console 0 420 K
csrss.exe 764 Console 0 4,876 K
winlogon.exe 788 Console 0 3,268 K
services.exe 836 Console 0 3,932 K
. . .
可以看到,输出包含不同于who命令的输出信息,但格式是类似的,所以可以考虑之前的方案:在一个或多个空白符上执行re.split()(此处没有制表符的问题)。
问题是命令名称可能有一个空白符,而且我们(应当)更倾向于将整个命令名称连接在一起。对于内存的使用也有这个问题,我们通常得到的是“NNN K”,其中NNN是内存数量大小,K表示千字节。我们也希望将这些数据连接在一起,因此,最好分隔至少一个空白符,对吧?
不,不能这样做。注意,进程 ID(PID)和会话名称列仅仅由一个空白符分隔。这就意味着如果去掉至少一个空白符,PID和会话名称将被合并在一起作为单个结果。如果复制之前的一个脚本,重命名它为retasklist.py,然后将who命令修改为tasklist /nh(/nh选项将会去除每一列的标题),并使用一个ss+正则表达式,就将得到如下所示的输出。
Z:\corepython\ch1>python retasklist.py
['']
['System Idle Process', '0 Console', '0', '28 K']
['System', '4 Console', '0', '240 K']
['smss.exe', '708 Console', '0', '420 K']
['csrss.exe', '764 Console', '0', '5,028 K']
['winlogon.exe', '788 Console', '0', '3,284 K']
['services.exe', '836 Console', '0', '3,924 K']
. . .
已经确认,尽管我们将命令名称和内存使用字符串保存在一起,但也不经意地将PID和会话名称放在一起。因此我们不得不放弃使用split函数,而且通过正则表达式匹配实现。我们可以这样实现,然后滤除会话名称和编号,因为两者都会为输出添加数值。示例1-4显示Python 2版本下retasklist.py的最终版本。
示例1-4 处理DOS环境下tasklist命令的输出(retasklist.py)
这里的脚本使用一个正则表达式和findall()来解析DOS环境下tasklist命令的输出,但是仅仅显示感兴趣的数据。将该脚本移植到Python 3时,仅仅需要修改print()函数。
如果运行这个脚本,就能得到期望(已截断)的输出。
Z:\corepython\ch1>python retasklist.py
[]
[('System Idle Process', '0', '28 K')]
[('System', '4', '240 K')]
[('smss.exe', '708', '420 K')]
[('csrss.exe', '764', '5,016 K')]
[('winlogon.exe', '788', '3,284 K')]
[('services.exe', '836', '3,932 K')]
. . .
细致的正则表达式将会扫描全部的5列输出字符串,仅对重要的数据进行分组:命令名称、命令相应的PID,以及该命令使用的内存大小。该脚本使用已经在本章中介绍过的正则表达式的很多特性。
显然,在本小节中实现的全部脚本只向用户显示输出。实际上,我们有可能在处理数据,并将数据保存入数据库,使用得到的输出来为管理层生成报表等。