GNOME デスクトップのユーザーにとって、Nautilus プログラムはおそらく最も頻繁に使用しているアプリケーションの 1 つでしょう。Nautilus では、単純なグラフィカル・インターフェースで、ファイルのコピー、移動、名前変更、検索といった日常的なすべての作業に対処します。一見したところ、Nautilus に不可能なファイル関連の操作はそれほど多くないように思えますが、通常シェル・スクリプトで行うタスクについて考えてみると、そうとも言えません。
Nautilus の開発者たちは、メインのコード・ベースに手を付けずに新しい機能を追加する方法をいくつか用意しました。そのうち最も簡単なのは、bash またはシェル・スクリプトを使用して、通常はターミナル・プロンプトから実行するような一連のコマンドを実行する方法です。この方法では、最初にコマンドを試して、コマンドによって意図したとおりの処理が実行されることを確認できます。さらに、シェル・スクリプト以外にも、C スクリプト言語や GnomeBasic、そして Perl、Python などの言語を使用することができます。そのなかから、この記事では Python 言語を使用して、Nautilus に新しい機能を追加する方法を説明します。記事では、読者に Python 言語および Python 標準ライブラリーの基礎知識があることを前提とします。
最初に紹介する 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 でのファイルの選択
図 2 に、Filecount.py スクリプトを実行して生成された出力を示します。
図 2. 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 の拡張機能はファイルの表示ウィンドウに組み込めるので、これまでは使用できなかった情報を列に取り込むための拡張機能を作成することができます。拡張機能を作成するために必要な最初の作業は、以下のコマンドを使って python-nautilus パッケージをインストールすることです。
sudo apt-get install python-nautilus |
このコマンドによって、必要なファイルがダウンロードおよびインストールされます。これらのファイルのなかには、マニュアルとサンプル・コードもあります。サンプル・コードが置かれている場所は、/usr/share/doc/python-nautilus/examples ディレクトリーです。python-nautilus パッケージがインストールされると、プログラミング対象の一連の 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
(表示列)」の順にクリックすると、新しい選択肢が表示されます。「Visible Columns
(表示列)」は、表示タイプを「List
(リスト)」に設定していなければ表示されません。「Block size (ブロック・サイズ)」列のチェック・ボックスを選択して、この列を有効にすると、以下の Python ライブラリー呼び出しの実行結果が表示されます。
str(os.stat(filename).st_blksize)) |
すべての Python 拡張機能に共通の基本パターンでは、既存の Nautilus プロバイダー基底クラスをサブクラス化した上で、最終的に該当する Nautilus
オブジェクトを返す一連の命令を実行します。block-size-column.py の例の場合、返されるオブジェクトは nautilus.Column なので、Nautilus には、name、attribute、label、description を含め、4 つのパラメーターを渡す必要があります。以下に、この例の 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
(プロパティー)」をクリックすると、ファイルに含まれる行数と文字数が表示されるようにします。この拡張機能の背後にある基本概念は、ファイル内の行数と文字数をカウントして、その結果をファイル・プロパティー・ページの新しいタブでレポートするというものです。拡張機能は、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 (行カウント)」タブをクリックして表示されたファイル内の行数
最後に、この拡張機能ユーティリティーを改造して、プロパティー・ページではなく、列にデータが取り込まれるようにします。コードに加える変更はわずかですが、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'] |
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 (行カウント)」列
Python で Nautilus を拡張するプロセスは単純明快です。Python の利点と簡潔さ、そして Python 標準ライブラリーは、効率的で読みやすいコードを作成するのに役立ちます。gnome.org サイトでドキュメントや例をナビゲートするのは難しいかもしれませんが、不可能なことではありません。多少のGoogle 検索で、他の例も見つかるはずです。この記事に記載した例から、Nautilus を特定の要求に合わせて拡張する方法を理解していただけたことを願います。Python のコーディングに慣れれば、問題は何もありません。
学ぶために
- Nautilus の詳細を学んでください。
- Nautilus-python 拡張機能の詳細を学んでください。
- Python.org で Python
のリソースを他にも見つけてください。
- developerWorks Linux ゾーンで、Linux
開発者および管理者向けのハウツー記事とチュートリアル、そしてダウンロード、ディスカッション、フォーラムなど、豊富に揃った資料を探してください。
- さまざまな IBM 製品および IT 業界についての話題に絞った developerWorks
の Technical events and webcasts で時代の流れをキャッチしてください。
- 無料の
developerWorks Live! briefing に参加して、IBM の製品およびツール、そして IT 業界の傾向を素早く学んでください。
- developerWorks の
on-demand demos で、初心者向けの製品のインストールとセットアップから、熟練開発者向けの高度な機能に至るまで、さまざまに揃ったデモを見てください。
- Twitter で developerWorks をフォローするか、developerWorks
で Linux に関するツイートのフィードに登録してください。
製品や技術を入手するために
- ご自分に最適な方法で IBM
製品を評価してください。評価の方法としては、製品の評価版をダウンロードすることも、オンラインで製品を試してみることも、クラウド環境で製品を使用することもできます。また、SOA Sandbox では、数時間でサービス指向アーキテクチャーの実装方法を効率的に学ぶことができます。
議論するために
- My developerWorks コミュニティーに加わってください。ここでは他の developerWorks ユーザーとのつながりを持てる他、開発者が主導するブログ、フォーラム、グループ、ウィキを調べることができます。