编写 Linux 桌面脚本,第 2 部分

编写 Nautilus 脚本

用 Python 扩展和延伸 Nautilus

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: 编写 Linux 桌面脚本,第 2 部分

敬请期待该系列的后续内容。

此内容是该系列的一部分:编写 Linux 桌面脚本,第 2 部分

敬请期待该系列的后续内容。

对于 GNOME 桌面用户来说,Nautilus 编程可能是更比较常用的应用程序之一。它能够通过简单的图形界面,来处理所有的文件复制、移动、重命名、以及搜索的问题。从表面上看,似乎不存在 Nautilus 不能处理的文件相关事务 — 除非考虑执行具有 shell 脚本的任务。

Nautilus 开发工具提供了多个不必打开主代码库而增加新功能的方法。最简单的方法是使用能执行那些通常在终端提示符上执行的命令的 bash 或者 bash 脚本。该方法使得尝试使用这一命令来确保他们完成想要首先完成的任务。还可以采用其他语言,包括 C 脚本语言、GnomeBasic、Perl、以及 Python。本文介绍如何利用 Python 语言来为 Nautilus 增加新功能。假定读者已对 Python 语言及 Python 标准库有所了解。

Nautilus 脚本

扩展 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 中选择文件
图片展示了当在 Nautilus 中选择了文件后的情况
图片展示了当在 Nautilus 中选择了文件后的情况

图 2 展示了 Filecount.py 脚本的运行结果。

图 2. Filecount.py 输出
图片展示了运行 Filecount.py 脚本的输出

在调试 Nautilus 脚本时,有几件事需要注意。第一件事是关闭 Nautilus 的所有实例,来使它完全重新加载,并找到新脚本或者扩展。可采用如下命令:

nautilus -q

下一个常用命令可实现不必打开首选或者配置数据,而直接运行 Nautilus。这在解决脚本或者扩展在无意间造成破坏之类的问题时,会节省很多步骤。命令如下:

nautilus -no-desktop

确保 filecount 工具可被 Nautilus 访问所剩的最后一步是将其复制到 ~/.gnome2/nautilus-scripts 目录,并改变文件代码来允许执行,相关命令是:

chmod +x Filecount.py

示例:创建文件 cleanup 工具

第二个例子是,创建文件 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 的第二个方法是通过创建扩展。此方法比第一个复杂,但有很多优势。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 个参数,包括 nameattributelabel、以及 description。本例子的 Python 代码是:

return nautilus.Column("NautilusPython::block_size_column", 
                       "block_size", 
                       "Block size", 
                       "Get the block size")

编写新扩展的代码涉及继承来自特定基本类的信息。 在 block-size-column.py 的例子中,nautilus.ColumnProvidernautilus.InfoProvider 在类定义中有举例,因此新类要从这两处继承。接下来需要覆盖来自基类或者类的任何方法来填充列。在 block-size-column.py 例子中,可通过覆盖 get_columnsupdate_file_info 方法来完成。

向 Nautilus 扩展传递信息的方法与脚本示例不同。Nautilus 实际上是启动新的流程来执行脚本,并设置多个环境变量来传递信息。在与 Nautilus 相同的流程中执行的扩展,能够访问对象、方法、和属性。通过 nautilus.FileInfo 传递的文件信息,包括 file_typelocationnameuri、以及 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 选项卡来查看文件的行数
图片展示了单击 Linecount 选项卡来查看文件行数时的情况
图片展示了单击 Linecount 选项卡来查看文件行数时的情况

最后,修改扩展函数来填充一列而不是整个属性页。因而代码的修改相当少,尽管需要同时从 nautilus.ColumnProvidernautilus.InfoProvider 继承。还必须执行 get_columnsupdate_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 列
图片展示了 Nautilus 窗口中的 Line Count 列
图片展示了 Nautilus 窗口中的 Line Count 列

结束语

利用 Python 来扩展 Nautilus 的确是个简单的过程。Python 与 Python 标准库非常的精巧,可用于编写高效而易读的代码。理解 gnome.org 站点中的文档与示例是很有挑战性的,但也不是不可能。Google 中的一些搜索结果也能提供一些例子。此处的例子可用于帮助您掌握如何扩展 Nautilus 来满足特定需求。如果您对 Python 编程很熟悉,那将不会有什么问题。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Linux
ArticleID=647512
ArticleTitle=编写 Linux 桌面脚本,第 2 部分: 编写 Nautilus 脚本
publish-date=04182011