对于 GNOME 桌面用户来说,Nautilus
编程可能是更比较常用的应用程序之一。它能够通过简单的图形界面,来处理所有的文件复制、移动、重命名、以及搜索的问题。从表面上看,似乎不存在
Nautilus 不能处理的文件相关事务 — 除非考虑执行具有 shell 脚本的任务。
Nautilus
开发工具提供了多个不必打开主代码库而增加新功能的方法。最简单的方法是使用能执行那些通常在终端提示符上执行的命令的 bash 或者
bash 脚本。该方法使得尝试使用这一命令来确保他们完成想要首先完成的任务。还可以采用其他语言,包括 C
脚本语言、GnomeBasic、Perl、以及 Python。本文介绍如何利用 Python 语言来为 Nautilus
增加新功能。假定读者已对 Python 语言及 Python 标准库有所了解。
扩展 Nautilus 的第一个方法是通过在 /home 中发现的名为
.gnome2/nautilus-scripts 的特定目录。当在 Scripts
菜单下的文件或者文件夹上点击鼠标右键时,该目录下所有可执行文件将会出现。还可以选择多个文件或者文件夹,并采用相同的右击方法,将文件清单传递给脚本。
当调用脚本时,Nautilus 支持多个包含当前目录以及所选文件等内容的环境变量。表 1 展示了这些环境变量。
表 1. Nautilus
环境变量
环境变量
描述
NAUTILUS_SCRIPT_SELECTED_FILE_PATHS
所选文件的新行分割路径(仅针对本地)
NAUTILUS_SCRIPT_SELECTED_URIS
所选文件的新行分割 URIs
NAUTILUS_SCRIPT_CURRENT_URI
当前位置
NAUTILUS_SCRIPT_WINDOW_GEOMETRY
当前窗口的位置和大小
在 Python 中,通过对 os.environ.get
函数的一个调用来获取这些变量的值,具体如下:
selected = os.environ.get('NAUTILUS_SCRIPT_SELECTED_FILE_PATHS,'')
此调用返回一个字符串,其包含到达由换行符分隔的全部所选文件。Python
利用下列代码,简化了将这一字符串返回到可迭代列表中的操作:
targets = selected.splitlines()
此时,也许应该停下来探讨一下用户交互。当控制从 Nautilus
传送到脚本后,在该点上确实不存在对脚本的限制。根据脚本作用的不同,甚至不需要任何用户反馈,除了一些类型的完成或错误消息,这样通过一些简单的消息框
就可处理好。由于在编写 Nautilus 时采用了 gtk windowing
工具包,所以尽管这不是必须的,但是采用相同的做法很合乎逻辑。您可以很方便地使用 TkInter 或者 wxPython。
鉴于本文的目的,您将采用
gtk。生成一个用于通信完成状态的简单消息框,仅需几行代码,出于方便阅读的目的,如果想创建简单的函数来生成消息,这个代码将最为合适。总共需要
4 行代码:
def alert(msg):
dialog = gtk.MessageDialog()
dialog.set_markup(msg)
dialog.run()
第一个示例程序将多个程序段合并成一个简单脚本,来返回当前所选文件的数量。这一脚本可用于文件或者目录。可利用另一个 Python
库函数,os.walk,递归地构建每个目录中文件的清单。总共有 38 行代码,如清单 1
所示,这就是这一小工具所需的全部内容,其中还包括了空行。
清单 1. 用于 Filecount 脚本的 代码
Python
#!/usr/bin/env python
import pygtk
pygtk.require('2.0')
import gtk
import os
def alert(msg):
"""Show a dialog with a simple message."""
dialog = gtk.MessageDialog()
dialog.set_markup(msg)
dialog.run()
def main():
selected = os.environ.get('NAUTILUS_SCRIPT_SELECTED_URIS', '')
curdir = os.environ.get('NAUTILUS_SCRIPT_CURRENT_URI', os.curdir)
if selected:
targets = selected.splitlines()
else:
targets = [curdir]
files = []
directories = []
for target in targets:
if target.startswith('file:///'):
target = target[7:]
for dirname, dirnames, filenames in os.walk(target):
for dirname in dirnames:
directories.append(dirname)
for filename in filenames:
files.append(filename)
alert('%s directories and %s files' %
(len(directories),len(files)))
if __name__ == "__main__":
main()
图 1 展示了当在文件上右击鼠标或者选择一组文件时所看到的内容。Scripts 菜单选项展示
.gnome2/nautilus-scripts 中所有的可执行文件,并给出了打开文件夹的选项。选择一个文件来执行该脚本。
图 1.在 Nautilus
中选择文件
图 2 展示了 Filecount.py 脚本的运行结果。
图 2. Filecount.py
输出
在调试 Nautilus 脚本时,有几件事需要注意。第一件事是关闭 Nautilus
的所有实例,来使它完全重新加载,并找到新脚本或者扩展。可采用如下命令:
nautilus -q
下一个常用命令可实现不必打开首选或者配置数据,而直接运行
Nautilus。这在解决脚本或者扩展在无意间造成破坏之类的问题时,会节省很多步骤。命令如下:
nautilus -no-desktop
确保 filecount 工具可被 Nautilus 访问所剩的最后一步是将其复制到
~/.gnome2/nautilus-scripts 目录,并改变文件代码来允许执行,相关命令是:
chmod +x Filecount.py
第二个例子是,创建文件 cleanup 工具,来查找任何可能由 Vim 或者 EMACS
之类的编辑器临时生成的文件。仅通过简单地修改 check
函数,就可利用相同的概念来清除任何特定文件的目录。这一代码属于静默操作,这意味着它执行后不向用户提供任何反馈。
该脚本的主函数看上去基本与前面具的示例相同,除了几个微不足道的异常。此代码会利用递归概念多次调用主函数,直至处理完最后一个目录为止。您可以采用
os.walk 函数,而不必采用递归来完成相同的任务。文件检查发生在 check
函数中,仅简单地检查以波浪号(~)或者井号(#)结束的文件,以井号后开始或扩展名
.pyc 结束的文件。该示例展示了 Python 标准库 os
模块所提供的数量众多的函数。它还提供了独立于操作系统方式来操作路径名和目录,以及执行文件操作的示例。清单 2
展示了该脚本的代码。
清单 2. 用于 cleanup 脚本的 Python
代码
#!/usr/bin/env python
import pygtk
pygtk.require('2.0')
import gtk
import os
def check(path):
"""Returns true to indicate a file should be removed."""
if path.endswith('~'):
return True
if path.startswith('#') and basename.endswith('#'):
return True
if path.endswith('.pyc'):
return True
return False
def walk(dirname=None):
selected = os.environ.get('NAUTILUS_SCRIPT_SELECTED_FILE_PATHS', '')
curdir = os.environ.get('NAUTILUS_SCRIPT_CURRENT_URI', os.curdir)
if dirname is not None:
targets = [dirname]
elif selected:
targets = selected.splitlines()
else:
targets = [curdir]
for target in targets:
if target.startswith('file:///'):
target = target[7:]
if not os.path.isdir(target): continue
for dirname, dirnames, files in os.walk(target):
for dir in dirnames:
dir = os.path.join(dirname, dir)
walk(dir)
for file in files:
file = os.path.join(dirname, file)
if check(file):
os.remove(file)
if __name__ == '__main__':
walk()
增强 Nautilus 的第二个方法是通过创建扩展。此方法比第一个复杂,但有很多优势。Nautilus
扩展可被内嵌到文件展示窗口中,那么就可以编写利用以前没有的信息来填充列的扩展。首先要做的就是利用如下命令安装
python-nautilus 扩展:
sudo apt-get install python-nautilus
此命令下载并安装所需的文件,包括文档和示例。可在目录
/usr/share/doc/python-nautilus/examples 中到找到示例代码。安装完成后,就可以访问一组
Nautilus 类和提供程序来再次对其进行编码。表 2 展示了该清单。
表 2. Nautilus
类与供应商
类或者供应商
描述
nautilus.Column
引用 Nautilus column 对象
nautilus.FileInfo
引用 Nautilus fileinfo 对象
nautilus.Menu
引用 Nautilus menu 对象
nautilus.MenuItem
引用 Nautilus menuitem 对象
nautilus.PropertyPage
引用 Nautilus propertypage 对象
nautilus.ColumnProvider
允许在 Nautilus 列中展示输出
nautilus.InfoProvider
提供关于文件的信息
nautilus.LocationWidgetProvider
展示位置
nautilus.MenuProvider
为右击菜单增加新功能
nautilus.PropertyPageProvider
为属性页面增加信息
gnome.org 站点上提供的示例展示了
MenuProvider(background-image.py 和
open-terminal.py)、ColumnProvider 以及
InfoProvider(block-size-column.py)、和
PropertyPageProvider(md5sum-property-page.py)的使用。ColumnProvider
采用 13 行 Python 可执行代码来向 Nautilus
引入新的列。一旦该代码被放置到合适的目录中(~/.nautilus/python-extensions)并且 Nautilus
已重启,在单击 View > Visible Columns
时将会看到新的选项。当将查看类型设置为 List 时,才会出现 Visible
Columns 选项。通过选择展示以下 Python 库调用结果的复选框,来启用 Block
size 列:
str(os.stat(filename).st_blksize))
任何 Python 扩展的基本模式都是对现有 Nautilus 提供程序基本类进行子类划分,然后执行一系列指令,并最终返回合适的
Nautilus 对象。在 block-size-column.py 例子中,返回的对象是
nautilus.Column。必须向 Nautilus 传递 4 个参数,包括
name、attribute、label、以及
description。本例子的 Python 代码是:
return nautilus.Column("NautilusPython::block_size_column",
"block_size",
"Block size",
"Get the block size")
编写新扩展的代码涉及继承来自特定基本类的信息。 在 block-size-column.py
的例子中,nautilus.ColumnProvider 与
nautilus.InfoProvider
在类定义中有举例,因此新类要从这两处继承。接下来需要覆盖来自基类或者类的任何方法来填充列。在 block-size-column.py
例子中,可通过覆盖 get_columns 和 update_file_info
方法来完成。
向 Nautilus 扩展传递信息的方法与脚本示例不同。Nautilus
实际上是启动新的流程来执行脚本,并设置多个环境变量来传递信息。在与 Nautilus
相同的流程中执行的扩展,能够访问对象、方法、和属性。通过 nautilus.FileInfo
传递的文件信息,包括
file_type、location、name、uri、以及
mime_type。想要向 FileInfo 对象增加信息,必须调用
add_string_attribute 方法。下面的例子是采用这一方法,来向
FileInfo 对象增加新的属性。
第一个例子使用 PropertyPageProvider
方法在文件(或多个文件)上单击右键显示行数和参数,然后单击
Properties。这一扩展背后的基本思想是计算文件中的行数和参数个数,并在文件属性页的新选项卡中报告结果。扩展可以直接访问了
Nautilus 数据结构,包括 file 对象。惟一要做的是利用
urllib.unquote 库函数来打开名字,操作如下:
filename = urllib.unquote(file.get_uri()[7:]
Python 中的一些行完成了对行及参数计数的主要工作。对于本例来说,创建 count
函数来将整个文件读取到一个大字符串中,然后计算参数数量及新添参数数量。因为属性页面可被显示为很多选中的文件及目录,所以必须预先计算多个文件。此时,惟一要做的就是将结果添加到属性页上的新页中。本例创建了示例
gtk.Hbox,然后利用获取的信息来填充大量标签,如清单 3 所示。
清单 3. Linecountextension.py
文件
import nautilus
import urllib
import gtk
import os
types = ['.py','.js','.html','.css','.txt','.rst','.cgi']
exceptions = ('MochiKit.js',)
class LineCountPropertyPage(nautilus.PropertyPageProvider):
def __init__(self):
pass
def count(self, filename):
s = open(filename).read()
return s.count('\n'), len(s)
def get_property_pages(self, files):
if not len(files):
return
lines = 0
chars = 0
for file in files:
if not file.is_directory():
result = self.count(urllib.unquote(file.get_uri()[7:]))
lines += result[0]
chars += result[1]
self.property_label = gtk.Label('Linecount')
self.property_label.show()
self.hbox = gtk.HBox(0, False)
self.hbox.show()
label = gtk.Label('Lines:')
label.show()
self.hbox.pack_start(label)
self.value_label = gtk.Label()
self.hbox.pack_start(self.value_label)
self.value_label.set_text(str(lines))
self.value_label.show()
self.chars_label = gtk.Label('Characters:')
self.chars_label.show()
self.hbox.pack_start(self.chars_label)
self.chars_value = gtk.Label()
self.hbox.pack_start(self.chars_value)
self.chars_value.set_text(str(chars))
self.chars_value.show()
return nautilus.PropertyPage("NautilusPython::linecount",
self.property_label, self.hbox),
图 3 展示了在文件上单击右键并单击 Linecount
选项卡的结果。此时,需要注意,这一特性可用于文件或者任何一组选定的文件和目录。所报告的数字将代表所有文件中的所有行。
图 3. 单击 Linecount
选项卡来查看文件的行数
最后,修改扩展函数来填充一列而不是整个属性页。因而代码的修改相当少,尽管需要同时从
nautilus.ColumnProvider 和
nautilus.InfoProvider 继承。还必须执行
get_columns 和 update_file_info。方法
get_columns 仅返回由方法 count 获取的信息。
方法 count 为列提供程序扩展采用不同的技术。Python 的
readlines 例程用于将一个文件的所有行读取到一列字符串中。计算行的总数就是在
len(s)
语句中返回的清单元素的数量。在两个例子中都要进行文件类型检查:这是要确保仅对包含需要计数行的文本文件进行计数。可利用如下行来创建一列可接受的文件扩展:
types = ['.py','.js','.html','.css','.txt','.rst','.cgi']
第二个清单包含了不会被计数的异常,对于本例来说,包含具有如下行的文件:
exceptions = ['MochiKit.js']
这两个清单用于包括或者排除具有如下两行代码的文件:
if ext not in types or basename in exceptions:
return 0
整个扩展需要 26 行可执行代码。您可能想要修改扩展,并输入清单来包含或者排除感兴趣的文件,清单 4 展示了完整的扩展。
清单 4. 用于 Linecountcolumn 扩展的
Python 代码
import nautilus
import urllib
import os
types = ['.py','.js','.html','.css','.txt','.rst','.cgi']
exceptions = ['MochiKit.js']
class LineCountExtension(nautilus.ColumnProvider, nautilus.InfoProvider):
def __init__(self):
pass
def count(self, filename):
ext = os.path.splitext(filename)[1]
basename = os.path.basename(filename)
if ext not in types or basename in exceptions:
return 0
s = open(filename).readlines()
return len(s)
def get_columns(self):
return nautilus.Column("NautilusPython::linecount",
"linecount",
"Line Count",
"The number of lines of code"),
def update_file_info(self, file):
if file.is_directory():
lines = 'n/a'
else:
lines = self.count(urllib.unquote(file.get_uri()[7:]))
file.add_string_attribute('linecount', str(lines))
图 4 显示了启用 Line Count 列的 Nautilus
窗口。每个单独的文件显示全部行数。您需要利用该方法进行一次计算就可以知道您总共需要多少个文件。
图 4. Nautilus 窗口中的 Line Count
列