Linux デスクトップのスクリプトの作成: 第 2 回 Nautilus のスクリプトを作成する

Python を使用して Nautilus を拡張する

この連載では、極めて生産性の高い環境を実現するために、Python を使用して GNOME デスクトップ、Screenlets フレームワーク、および Nautilus のスクリプトを作成する方法を探ります。デスクトップ上のスクリプトによって、ドラッグ・アンド・ドロップ機能が使用できるようになり、頻繁に使用する情報やサービスに素早くアクセスできるようになります。今回の記事では、Python を使用してデスクトップ上の Nautilus に機能を追加して拡張する方法を説明します。

Paul Ferrill (paul@ferrill.net), CTO, ATAC

Paul Ferrill は、これまで 20 年以上、コンピューター業界紙で著作活動を続けています。その出発点となったのは、LANtastic や初期のバージョンの Novell Netware などといった製品に関する PC Magazine でのネットワーキングのレビューでした。BSEE および MSEE の学位を両方取得した彼は、覚えていられないぐらい多くのコンピューター・プラットフォームおよびアーキテクチャーを対象としたソフトウェアを作成しています。



2011年 8月 19日

GNOME デスクトップのユーザーにとって、Nautilus プログラムはおそらく最も頻繁に使用しているアプリケーションの 1 つでしょう。Nautilus では、単純なグラフィカル・インターフェースで、ファイルのコピー、移動、名前変更、検索といった日常的なすべての作業に対処します。一見したところ、Nautilus に不可能なファイル関連の操作はそれほど多くないように思えますが、通常シェル・スクリプトで行うタスクについて考えてみると、そうとも言えません。

Nautilus の開発者たちは、メインのコード・ベースに手を付けずに新しい機能を追加する方法をいくつか用意しました。そのうち最も簡単なのは、bash またはシェル・スクリプトを使用して、通常はターミナル・プロンプトから実行するような一連のコマンドを実行する方法です。この方法では、最初にコマンドを試して、コマンドによって意図したとおりの処理が実行されることを確認できます。さらに、シェル・スクリプト以外にも、C スクリプト言語や GnomeBasic、そして Perl、Python などの言語を使用することができます。そのなかから、この記事では Python 言語を使用して、Nautilus に新しい機能を追加する方法を説明します。記事では、読者に Python 言語および Python 標準ライブラリーの基礎知識があることを前提とします。

Nautilus のスクリプトを作成する

最初に紹介する Nautilus の拡張手法では、/home にある、.gnome2/nautilus-scripts という名前の特殊なディレクトリーを使用します。ファイルまたはフォルダーを右クリックすると表示されるコンテキスト・メニューから「Scripts (スクリプト)」を選択すると、この nautilus-scripts ディレクトリー内に置かれたすべての実行可能ファイルが表示されます。1 度の右クリック操作で、複数のファイルまたはフォルダーを選択し、ファイルのリストをスクリプトに渡すこともできます。

Nautilus では、スクリプトを呼び出す際に、カレント・ディレクトリーや選択されたファイルなどの情報を含んだ環境変数をいくつか使用できるようになっています。表 1 に、これらの環境変数を記載します。

表 1. Nautilus の環境変数
環境変数説明
NAUTILUS_SCRIPT_SELECTED_FILE_PATHS改行で区切られた、選択されているファイルのパス (ローカルの場合のみ)
NAUTILUS_SCRIPT_SELECTED_URIS改行で区切られた、選択されているファイルの URI
NAUTILUS_SCRIPT_CURRENT_URIカレント・ディレクトリーの場所
NAUTILUS_SCRIPT_WINDOW_GEOMETRY現在表示しているウィンドウの位置とサイズ

Python では上記の変数の値を取得するには、以下のように os.environ.get 関数を 1 回呼び出すだけです。

selected = os.environ.get('NAUTILUS_SCRIPT_SELECTED_FILE_PATHS,'')

この呼び出しの場合、選択されているすべてのファイルのパスを改行文字で区切った 1 つのストリングが返されます。Python では、以下の行によって、このストリングを反復可能リストへと簡単に変換することができます。

targets = selected.splitlines()

ここで、ユーザーとの対話について説明しておきます。ひとたび Nautilus からスクリプトに制御が渡されると、それ以降はスクリプトの処理内容についてはまったく何も制約がありません。スクリプトの処理内容によっては、ユーザーからの応答が不要な場合すらありますが、何らかの完了メッセージまたはエラー・メッセージは必要です。これらのメッセージは、単純なメッセージ・ボックスを使って処理することができます。Nautilus は gtk ウィンドウ作成ツールを使用して作成されているため、同じツールを使用するのが当然の選択のように思えますが、必須ではありません。TkInter または wxPython を使用しても同じく簡単です。

この記事では gtk を使用します。完了ステータスを通知する単純なメッセージ・ボックスを作成するために必要なコードは、わずか数行です。読みやすさを考慮して、メッセージを生成する単純な関数を作成するとしたら、以下に記載するコードが最適でしょう。このように 4 行のコードで、メッセージを生成することができます。

def alert(msg):
    dialog = gtk.MessageDialog()
    dialog.set_markup(msg)
	dialog.run()

例: 選択されているファイルの数を返す、単純なスクリプトを作成する

最初に紹介するサンプル・プログラムは、上記に記載したスニペットを 1 つにまとめた単純なスクリプトです。現在選択されているファイルの数を返すこのスクリプトは、個別のファイルやディレクトリーにも使用することができます。ディレクトリーごとのファイルのリストを再帰的に作成するために、ここでは os.walk という別の Python ライブラリー関数も使用しています。リスト 1 に記載するように、この単純なユーティリティーに必要なのは、空白の行も含めてわずか 38 行のコードだけです。

リスト 1. ファイル・カウント・スクリプトの 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 に、に Nautilus でファイルを右クリックしたとき、またはファイルのグループを選択したときに表示されるメニューを示します。このメニューから「Scripts (スクリプト)」を選択すると、.gnome2/nautilus-scripts に置かれているすべての実行可能ファイルと、このフォルダーを開くという選択肢が表示されます。このなかから、いずれかのファイルを選択すると、そのスクリプトが実行されます。

図 1. Nautilus でのファイルの選択
Nautilus でファイルを選択するのに続いて表示されるメニュー

図 2 に、Filecount.py スクリプトを実行して生成された出力を示します。

図 2. Filecount.py の出力
Filecount.py スクリプトを実行した結果、表示された出力

この Nautilus スクリプトのデバッグに取り組むときに役立つ操作がいくつかあります。まずその 1 つは、Nautilus のすべてのインスタンスを閉じるという操作です。これによって、Nautilus が完全にリロードを実行できるため、新しいスクリプトや拡張機能を調べることができます。この操作は、以下のコマンドで実行することができます。

nautilus -q

もう 1 つの便利なコマンドでは、設定またはプロファイル・データを開くことなく Nautilus を実行できるため、スクリプトや拡張機能によって思いがけず何かが破損された場合に、いくつかのステップを省くことができます。そのコマンドは以下のとおりです。

nautilus -no-desktop

この filecount ユーティリティーに Nautilus からアクセスできるようにするための最後のステップは、ユーティリティーを ~/.gnome2/nautilus-scripts ディレクトリーにコピーして、ファイル・モードを実行可能モードに変更することのみです。それには、以下のコマンドを使用します。

chmod +x Filecount.py

例: ファイル・クリーンアップ・ユーティリティーを作成する

2 番目の例として作成するファイル・クリーンアップ・ユーティリティーは、Vim や EMACS などのエディターによって一時的に生成されたと考えられるすべてのファイルを見つけ出します。特定のファイルのディレクトリーをパージするには、前の例と同じ概念を適用して、単に check 関数を変更するだけです。このコードは、サイレント操作のカテゴリーに分類されます。サイレント操作とは、コードがユーザーに応答を返さずに処理を実行することを意味します。

このスクリプトの main 関数は、多少の例外を除けば、基本的に前の例と同じです。再帰の概念を適用したこのコードは、最後のディレクトリーがトラバースされるまで、main 関数を繰り返し呼び出します。再帰を使用しないで同じタスクを行うとしたら、os.walk 関数を使用することもできます。check 関数で行われるファイルのチェックでは、ファイル名がチルダ (~) またはシャープ (#) で終わるファイル、シャープで始まるファイル、または拡張子 .pyc で終わるファイルを探します。この例には、Python 標準ライブラリーのos モジュールが提供する多数の関数が披露されています。これはまた、特定のオペレーティング・システムに依存しないでパス名およびディレクトリーを操作するとともに、ファイル操作を実行する好例でもあります。リスト 2 に、このスクリプトのコードを記載します。

リスト 2. クリーンアップ・スクリプトの 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 ディレクトリーです。python-nautilus パッケージがインストールされると、プログラミング対象の一連の Nautilus クラスおよびプロバイダーにアクセスできるようになります。表 2 に、これらのクラスおよびプロバイダーの一覧を示します。

表 2. Nautilus のクラスおよびプロバイダー
クラスまたはプロバイダー説明
nautilus.ColumnNautilus の column オブジェクトへの参照
nautilus.FileInfoNautilus の fileinfo オブジェクトへの参照
nautilus.MenuNautilus の menu オブジェクトへの参照
nautilus.MenuItemNautilus の menuitem オブジェクトへの参照
nautilus.PropertyPageNautilus の propertypage オブジェクトへの参照
nautilus.ColumnProviderNautilus の列に出力を表示するために使用します。
nautilus.InfoProviderファイルに関する情報を提供します。
nautilus.LocationWidgetProvider場所を表示します。
nautilus.MenuProvider右クリック・メニューに新しい機能を追加します。
nautilus.PropertyPageProviderプロパティー・ページに情報を追加します。

gnome.org サイトに提供されている例に、MenuProvider (background-image.py および open-terminal.py)、ColumnProviderInfoProvider (block-size-column.py)、そして PropertyPageProvider (md5sum-property-page.py) の使用方法が説明されています。ColumnProvider は 13 行の Python 実行可能コードを使用して、新しい列を Nautilus に組み込みます。このコードを適切なディレクトリー (~/.nautilus/python-extensions) に置いて Nautilus を再起動した後、「View (表示)」 > 「Visible Columns (表示列)」の順にクリックすると、新しい選択肢が表示されます。「Visible Columns (表示列)」は、表示タイプを「List (リスト)」に設定していなければ表示されません。「Block size (ブロック・サイズ)」列のチェック・ボックスを選択して、この列を有効にすると、以下の Python ライブラリー呼び出しの実行結果が表示されます。

str(os.stat(filename).st_blksize))

すべての Python 拡張機能に共通の基本パターンでは、既存の Nautilus プロバイダー基底クラスをサブクラス化した上で、最終的に該当する Nautilus オブジェクトを返す一連の命令を実行します。block-size-column.py の例の場合、返されるオブジェクトは nautilus.Column なので、Nautilus には、nameattributelabeldescription を含め、4 つのパラメーターを渡す必要があります。以下に、この例の 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_columns および update_file_infoメソッドをオーバーライドします。

Nautilus 拡張機能に情報を渡す仕組みは、スクリプトを作成する場合とは異なります。実際、Nautilus は新しいプロセスを起動してスクリプトを実行し、環境変数をいくつも設定して情報を渡せるようにします。拡張機能は Nautilus と同じプロセスで実行することから、オブジェクト、メソッド、および属性にアクセスします。ファイルに関する情報は、nautilus.FileInfo オブジェクトによって渡されます。この情報には、file_typelocationnameurimime_type などが含まれます。FileInfo オブジェクトに情報を追加するには、add_string_attribute メソッドを呼び出す必要があります。次の例では、この手法を使用して新しい属性を FileInfo オブジェクトに追加します。

例: ファイル内の行数を表示する

最初の例では PropertyPageProvider メソッドを使用して、ファイル (複数可) を右クリックした後に「Properties (プロパティー)」をクリックすると、ファイルに含まれる行数と文字数が表示されるようにします。この拡張機能の背後にある基本概念は、ファイル内の行数と文字数をカウントして、その結果をファイル・プロパティー・ページの新しいタブでレポートするというものです。拡張機能は、file オブジェクトをはじめとする Nautilus データ構造に直接アクセスすることができます。したがって、ここで必要となる作業は、以下のurllib.unquote ライブラリー関数を使用してファイル名をアンパックすることだけです。

filename = urllib.unquote(file.get_uri()[7:]

行数と文字数をカウントするメインの操作は、わずか数行の Python コードで行います。この例の場合、ファイル全体を 1 つの大きなストリングに読み込んでから文字数の合計と改行文字の合計をカウントする 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 (行カウント)」タブをクリックしてファイル内の行数を表示する画面のスクリーン・キャプチャー

最後に、この拡張機能ユーティリティーを改造して、プロパティー・ページではなく、列にデータが取り込まれるようにします。コードに加える変更はわずかですが、nautilus.ColumnProvidernautilus.InfoProvider の両方を継承する必要があります。さらに、get_columnsupdate_file_info を実装する必要もあります。get_columns メソッドは、count メソッドによって収集された情報を返すだけにすぎません。

この場合の count メソッドは、列プロバイダーの拡張機能とは別の手法を使用し、Python の readlines ルーチンによってファイル内のすべての行をストリングのリストに読み込みます。行の合計数をカウントした結果は、len(s) ステートメントで返されるリスト内の要素数そのものとなります。また、ファイル・タイプのチェックは両方の例に共通しているわけではありません。ファイル内の行数をカウントすることに意味があるのは、実際にカウントする行が含まれているテキスト・ファイルを対象とする場合のみなので、以下の行によって、行数のカウントを許容するファイル拡張子のリストを作成します。

types = ['.py','.js','.html','.css','.txt','.rst','.cgi']

2 番目のリストには、カウントしない例外ファイルを含めます。この例の場合には、以下の行で 1 つのファイルを除外します。

exceptions = ['MochiKit.js']

以下の 2 行のコードで、上記の 2 つのリストを使用してファイルをカウントに含めるか、あるいは除外します。

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 (行カウント)」列を示す画面のスクリーン・キャプチャー

まとめ

Python で Nautilus を拡張するプロセスは単純明快です。Python の利点と簡潔さ、そして Python 標準ライブラリーは、効率的で読みやすいコードを作成するのに役立ちます。gnome.org サイトでドキュメントや例をナビゲートするのは難しいかもしれませんが、不可能なことではありません。多少のGoogle 検索で、他の例も見つかるはずです。この記事に記載した例から、Nautilus を特定の要求に合わせて拡張する方法を理解していただけたことを願います。Python のコーディングに慣れれば、問題は何もありません。

参考文献

学ぶために

製品や技術を入手するために

  • ご自分に最適な方法で IBM 製品を評価してください。評価の方法としては、製品の評価版をダウンロードすることも、オンラインで製品を試してみることも、クラウド環境で製品を使用することもできます。また、SOA Sandbox では、数時間でサービス指向アーキテクチャーの実装方法を効率的に学ぶことができます。

議論するために

  • My developerWorks コミュニティーに加わってください。ここでは他の developerWorks ユーザーとのつながりを持てる他、開発者が主導するブログ、フォーラム、グループ、ウィキを調べることができます。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux
ArticleID=751084
ArticleTitle=Linux デスクトップのスクリプトの作成: 第 2 回 Nautilus のスクリプトを作成する
publish-date=08192011