在我以前撰写的一篇 IBM developerWorks 文章 中,介绍了如何用 Python 创建命令行工具。本文将创建插件来扩展这些命令行工具,从而把它们提高到更高水平。插件和命令行工具都提供了扩展现有代码功能的简便方法。它们结合在一起可以形成非常强大的工具。
为了开始编写插件,我们要使用我编写的开放源码 Python 包 pathtool,这个库使用生成器操作文件系统并产生一个文件对象。这个库允许开发人员编写自己的过滤器来扩展它,过滤器对文件对象做一些处理,然后返回结果。
实际的 Python 模块代码比较长,不适合在本文中给出,所以只介绍开发人员实际使用的 API 片段:
清单 1. pathtool API
def path(fullpath, pattern="*", action=(lambda rec: print_rec(rec))):
"""This takes a path, a shell pattern, and an action callback
This function uses the slower pathattr function which calculates checksums
"""
for rec in pathattr(fullpath):
for new_record in match(pattern, rec): #applies filter
action(new_record) #Applies lambda callback to generator object
|
看一下这个示例,可以看出这个路径函数有一个必需的路径位置参数,还有一个可选的模式关键字参数和一个可选的动作关键字参数(称为 lambda 回调函数)。路径的默认回调函数仅仅输出文件名。开发人员只需要执行 easy_install 命令。关于使用 easy_install 命令的信息参见 参考资料。然后执行以下命令导入这个模块并调用函数:
from pathtool import path
path("/tmp", pattern="*.mp3", action=(lambda rec: print_rec(rec)))
|
注意:本文提供了 pathtool 的 源代码。这个示例的关键点是使用 lambda。在 参考资料 中可以找到关于 lambda 的 Python 教程,但是简单地说,lambda 是让一个函数 “调用” 另一个函数的简便方法。
我们已经基本了解了如何使用这个包含回调函数的路径操作库,现在要编写一个可以用插件扩展的命令行工具。先看一下完成后的版本,然后分析其组成部分:
清单 2. 带插件的命令行工具
#!/usr/bin/env python
# encoding: utf-8
"""
pathtool-cli.py 0.1
A commandline tool for walking a filesystem.
Takes Action callback plugins in a plugin directory
action=(lambda rec: print_rec(rec))
"""
from pathtool import path
import optparse
import re
import os
import sys
try:
plugin_available = True
from plugin import *
from plugin import __all__ #note this is the registered plugin list
except ImportError:
plugin_available = False
def path_controller():
descriptionMessage = """
A command line tool for walking a filesystem.\
Takes callback 'Action' functions as plugins.\
example: pathtool_cli /tmp print_path_ext
"""
p = optparse.OptionParser(description=descriptionMessage,
prog='pathtool',
version='pathtool 0.1.1',
usage= '%prog [starting directory][action]')
p.add_option('--pattern', '-p',
help='Pattern Match Examples: *.txt, *.iso, music[0-5].mp3\
plain number defaults to * or match all. \
Uses UNIX standard wildcard syntax.',
default='*')
p.add_option('--list', '-l',
action="store_true",
help='lists available action plugins',
default=False)
options, arguments = p.parse_args()
if options.list:
try:
print "Action Plugins Available:"
if plugin_available:
for p in __all__:
print p
finally:
sys.exit(0)
if len(arguments) == 2:
fullpath = arguments[0]
try:
action_plugin = eval(arguments[1])
#note we expect the plugin author to write a method with our naming convention
#path(fullpath,options.pattern,action=(lambda rec: move_to_tmp.plugin(rec)))
path(fullpath, options.pattern,action=(lambda rec: action_plugin.plugin(rec)))
except NameError:
sys.stderr.write("Plugin Not Found")
sys.exit(1)
else:
print p.print_help()
def main():
path_controller()
if __name__ == '__main__':
main()
|
运行这个示例会产生以下输出:
# python pathtool_cli.py
Usage: pathtool [starting directory][action]
A command line tool for walking a filesystem. Takes callback 'Action'
functions as plugins. example: pathtool_cli /tmp print_path_ext
Options:
--version show program's version number and exit
-h, --help show this help message and exit
-p PATTERN, --pattern=PATTERN
Pattern Match Examples: *.txt, *.iso, music[0-5].mp3
plain number defaults to * or match all.
Uses UNIX standard wildcard syntax.
-l, --list lists available action plugins
|
在这个命令的输出中可以看到,这个工具需要一个完整路径,然后是一个 “动作”。动作是开发人员创建的一个插件。我增加了一个命令行列表选项,让这个命令行工具的用户可以看到可用的插件。看一下它的输出:
# python pathtool_cli.py -l Action Plugins Available: move_to_tmp print_file_path_ext |
即使不太了解这个工具的工作原理,也能够通过动作的名称猜出它会执行哪些操作。我编写的 print_file_path_ext 动作仅仅输出路径、文件名和扩展名,运行它,看看它的输出:
# python pathtool_cli.py /tmp print_file_path_ext /tmp/foo0.txt | foo0.txt | .txt /tmp/foo1.txt | foo1.txt | .txt /tmp/foo10.txt | foo10.txt | .txt /tmp/foo2.txt | foo2.txt | .txt /tmp/foo3.txt | foo3.txt | .txt /tmp/foo4.txt | foo4.txt | .txt /tmp/foo5.txt | foo5.txt | .txt /tmp/foo6.txt | foo6.txt | .txt /tmp/foo7.txt | foo7.txt | .txt /tmp/foo8.txt | foo8.txt | .txt /tmp/foo9.txt | foo9.txt | .txt |
我使用 touch foo{0..10}.txt 创建了十一个临时文件,现在这个命令行工具使用它找到的一个插件显示完整路径、文件名和扩展名(以 “|” 字符分隔)。
到目前为止,我只讨论了如何使用这个工具,还没有解释这些插件的工作原理。先看看这个模块顶部的导入语句:
plugin_available = True
from plugin import *
from plugin import __all__ #note this is the registered plugin list
except ImportError:
plugin_available = False
|
这个导入语句揭示了这个极其简单的插件体系结构的秘密。一般情况下,Python 官方文档不鼓励使用 “from package import *” 语法,但是如果有合理理由的话(比如编写插件),可以这样做。插件作者负责在插件目录中的 __init__.py 文件中创建一个条目。这个条目应该像下面这样:
"""Lists all of the importable plugins""" __all__ = ["move_to_tmp", "print_file_path_ext"] |
通过创建这个条目,可以以 * 的形式导入包(或目录)中的所有模块。接下来,导入实际的 __all__ 列表,向用户显示可用的插件。最后,还需要一行代码。因为直到运行之前命令行工具并不知道要使用哪个插件动作,所以要使用 eval 把命令行上的动作字符串转换为一个可调用的函数,如下所示:
action_plugin = eval(arguments[1]) |
在一般情况下,应该极其谨慎地使用 eval,但是在这里通过 eval 告诉工具使用哪些插件方法是合理的。
既然已经了解了这个插件体系结构的工作原理,就来看看实际的插件。注意,为了让这个体系结构发挥作用,需要在当前工作目录或 Python site-packages 目录中创建一个插件目录。我们要讨论的插件称为 print_file_path_ext.py,它包含一个称为 plug-in 的方法。这是插件开发人员必须满足的 API 要求。
清单 3. 插件示例
#!/usr/bin/env python
# encoding: utf-8
"""
prints path, name, ext, plugin
"""
def plugin(rec, verbose=True):
"""Moves matched files to tmp directory"""
path = rec["path"]
filename = rec["filename"]
ext = rec["ext"]
print "%s | %s | %s" % (path, filename, ext)
|
这个插件非常简单。它有一个 rec 参数,这个参数是 pathtool 模块生成的词典。这个词典包含以下 API:
{"path": path, "filename": file, "ext": ext, "size": size,
"unique_id": unique_id, "mtime": mtime, "ctime": ctime}
|
在这个示例中,每当调用它时,使用词典的键输出特定文件对象的值。插件作者可以编写许多更有用的动作,比如对文件进行转换、重命名、存档等等。
本文介绍了一个非常简单的插件体系结构,可以通过它用 Python 扩展命令行工具。但是,应该注意几点。首先,可以通过 easy_install 使用一个更高级的插件系统(参见参考资料)。这个插件系统允许用户创建 “入口点” 来为工具定义插件。第二,我们的命令行工具只允许一个 “动作” 插件。可以修改这个命令行工具,让它能够接受数量不限的 “链式” 回调动作,这留给读者作为练习。
关于创建链式插件还有一个问题:设计必须考虑到使用的 API 的性质。在我们的示例中,以生成器作为基础。为了让这个工具能够把插件 “链接” 在一起,各个插件必须完成本身的工作,然后生成词典记录。我希望本文能够鼓励您为命令行工具编写自己的插件。
| 描述 | 名字 | 大小 | 下载方法 |
|---|---|---|---|
| 示例 CLI 插件代码 | cli_plugin_code.zip | 15KB | HTTP |
学习
- 阅读 Ian Bicking PyCon Plug-in Presentation。
- Creating command-line tools in Python(developerWorks,2008 年 3 月):学习如何创建简单的命令行工具。
- Plug-in:阅读关于插件的 Wikipedia 文章。
- Firefox Plug-in:帮助浏览器执行特殊功能,比如查看特殊的图形格式或播放多媒体文件。
- 用 Setuptools 动态地发现服务和插件,从而支持创建能够 “插入” 到可扩展的应用程序和框架中的库。
- Python Lambda
Statements 可以用在需要函数对象的任何地方。
- Pathtool 是一种用来操作文件系统的高效的 API。
-
AIX 和 UNIX developerWorks 专区 提供与 IBM® AIX® 系统管理的所有方面相关的大量信息,您可以利用它们来提高自己的 UNIX 技能。
-
AIX 和 UNIX 新手入门:访问 “AIX 和 UNIX 新手入门” 页面可以了解更多关于 AIX 和 UNIX 的内容。
-
developerWorks 技术获得和网络广播:随时关注 developerWorks 技术活动和网络广播。
-
Podcasts:收听 IBM 技术专家的访谈录。
获得产品和技术
-
IBM 试用软件:使用可从 developerWorks 直接下载的试用软件开发您的下一个项目。
讨论
- 参与 AIX 和 UNIX 论坛:
- AIX 论坛
- AIX for developers 论坛
- Cluster Systems Management
- IBM Support Assistant 论坛
- 性能工具论坛
- 虚拟化论坛
- 更多的 AIX 和 UNIX 论坛

Noah Gift 是 O'Reilly 出版的“Python For Unix and Linux”一书的合著者。他是一名作家、演说家、顾问和社区负责人,并为 IBM developerWorks、Red Hat Magazine、O'Reilly 和 MacTech 众多出版商撰稿。他的咨询公司的网站是 www.giftc.com,他的个人网站是 www.noahgift.com。Noah 目前还是 www.pyatl.org 网站的组织者,该网站是佐治亚州亚特兰大的 Python 用户组。他拥有加州洛杉矶的 CIS 的硕士学位,加州 Poly San Luis Obispo 的营养科学学士学位,他还是通过 Apple 和 LPI 认证的系统管理员,他曾经在许多公司工作过,如加利福尼亚理工学院、Disney Feature Animation、Sony Imageworks 和 Turner Studios。在空闲的时候,他喜欢和妻子 Leah,以及他们的儿子 Liam 一起度过,弹奏钢琴以及进行宗教活动。