ソースコード・リテラシーのススメ

第19回ソフトウェア・ツールズの活用

前回紹介したPlamo Linux 4.5は、無事10月16日に正式公開できました。これで開発も一段落、と言いたいところですが、すでにX11R7.4やPython-2.6など、重要なソフトウェアがバージョンアップされてしまったため、さっそくそれらに追従する作業を始めています。

ディストリビューションのまとめ役というのは因果な稼業で、リリース前にはテストのためにβ版を何度となくインストールする一方で、リリースが終わればさっそく新しいパッケージのビルドやテストで中身が入れ替ってしまい、苦労して作ったリリース版を自分で使っている時間はほとんど無かったりします(苦笑⁠⁠。

このような作業の中でGNOMEやKDEの新版などをいじっていると、最近のGUIツールの充実には目を見張るものがあります。一般ユーザの作業だけでなく、システム管理レベルの作業の多くがGUI経由で行えるようになり、rootの概念もかってのUNIXの世界とはずいぶん変ってきたように感じます。

GUIツールは初心者にも取っつきやすく、あらかじめ決められた定型的な作業をするには便利ですが、同じような作業を何度もくりかえしたり、想定外の作業をしようとすると途端に面倒になります。

筆者のようにディストリビューションのまとめ役という作業をやっていると、一般ユーザーには必要ない類いの作業が多数発生します。今回もX11R7.4への更新作業中に、7.4として配布されているパッケージだけではX Window Systemを動作させることができないことに気づき、7.3から7.4で更新されたパッケージとそうでないパッケージを調べる必要が生じました。

このような例外的な作業をGUIツールでサポートすることは困難ですし、かといって、全てのパッケージをひとつひとつ調べていくのも現実的ではありません。コンピュータを使いこんでいくと、この手の「専用ツールを用意するほどではないけれど、手作業で処理するのは大変」といった規模の作業によく出会います。このような作業に力を発揮するのがソフトウェア・ツールズ(software tools)です。

ソフトウェア・ツールズとは

以前にも簡単に紹介したことがありますがソフトウェア・ツールズとは、Linuxの元となったUNIXの世界で広く用いられていた概念で、何でもできる大きなソフトウェアではなく、ひとつの機能に特化した小さなソフトウェアを組み合わせて必要な機能を実現しようという考え方です。

UNIXが生まれたころのコンピュータは、現在とは比べものにならないほどCPUの能力もメモリの容量も貧弱で、メモリやCPUを大量に必要とする高機能で大きなソフトウェアよりも、一つの機能をうまくこなす小さなソフトウェアが好まれました。

開発者たちが必要に応じて作ったそれら小さなソフトウェアは、次第に取捨選択され、洗練を重ねて、UNIXの基本コマンドに成長しました。これらの基本コマンドを組み合わせて複雑な処理もやってしまおう、というのがソフトウェア・ツールズの考え方です。

ソフトウェア・ツールズとしてどのようなコマンドを多用するかは人それぞれですし、よく行う作業によって必要となるコマンド類も異なってくるでしょう。以下では筆者がよく使うコマンドとオプションを簡単に紹介します。

grep
引数として指定した文字列がファイルに含まれるかを検索するコマンド。どのソースコードのどこに指定した文字列があるかを調べることができる。指定した文字列を含まないファイルを表示したり(-v⁠⁠、文字列を含むファイル名だけを表示(-l)することも可能。
sed
ストリームエディタ。ファイルの中の文字列を別の文字列に置き替える際に利用。^ で行頭、$ で行末を意味する等の正規表現も指定可能。
cut
行の分割コマンド。-d オプションで区切り記号(デリミタ)を指定できる。区切り記号を空白文字(-d' ')にすれば英文を単語に分割できるし、コロン(-d':')にすればパスワードファイルをフィールドごとに分割できる。分割した行は -f オプションで指定した部分のみを取り出すことができる。
sort
ソート(並び替え)コマンド。ファイルや標準入力の内容を指定したキーの順番に並び替えて出力する。キーの区切り記号や位置はオプションで変更可能。
uniq
同じ内容の行が連続する際にそれらを1行にまとめるコマンド。同じ行が何度出てきたかを数えることも可能(-c)で、sortと組み合わせて特定の文字列の出現頻度を数える際などに便利。
find
指定したディレクトリ以下にあるファイル群から条件に合うファイルを見つけるコマンド。条件はファイル名やファイルの種類、更新日時、大きさなど、さまざまに指定可能。条件を指定せず、あるディレクトリ以下のすべてのファイルの名前を表示させるという使い方も可能。
xargs
標準入力から読み込んだ一群のファイルに対して同じ操作を繰り返し行う際に便利なコマンド。findと組み合わせて、あるディレクトリ以下のすべてのファイルに対してgrepを行う、といった際に利用。
basename
パス名(たとえば /home/kojima/myfile.txt)からファイル名(myfile.txt)を取り出したり、ファイル名から拡張子の部分(.txt)を取り除いたりするコマンド。ファイルの拡張子を一括変換する際などに便利。
gawk
これまでに紹介したコマンドよりはスクリプト言語に近いツール。行単位でのパターンマッチやアクション指定などの機能を持ち、sed や cut の仕事の多くを代用することも可能。後発のPerlやPythonに比べると制御構造等が弱いので、大規模なコードを書くには向かない。

これらUNIXの基本コマンドは基本的に1つの機能しか持ちませんが、標準入力からデータを読み込み、加工した結果を標準出力に出力するという共通したデザインで設計されているため、あるコマンドの結果をパイプ(|)で次のコマンドに読み込ませて処理を重ね合わせ、1つ1つのステップは単純でも、全体ではずいぶん複雑な処理ができるようになっています。

単機能なコマンドを組み合わせて必要な機能を生み出していくのはパズルを解くような知的作業で、GUIならばマウスを何度もクリック、クリックしないといけない作業を、コマンドライン一発でさっと処理した時は、思わずガッツポーズを取りたくなるほどです(笑⁠⁠。

ソフトウェア・ツールズで難しいのは、これらUNIXの基本コマンドを使うかPerlやPythonといった本格的なスクリプト言語を使うかの見極めでしょう。数分で書ける書き捨てのコードは、これら基本コマンドを組み合わせたシェルスクリプトで書く方が楽ですが、同じような作業を将来も行う可能性がある場合は、多少時間をかけてもPythonくらいで書いておく方が後々まで使い回せて楽だったりします。今の楽さを取るか、将来、楽をするために今の苦労を取るかは、判断の難しいところです。

ソフトウェア・ツールズの使用例

ソフトウェア・ツールズの実例として、上述のX11R7.4への更新の際に生じたパッケージ確認作業を紹介しましょう。

Plamo-4.5ではリリース時期の都合でX11R7.3を採用していますが、9月の下旬にX11R7.4が公開されてしまったので、Plamo-4.5のリリースが終ると、さっそくX11R7.4のパッケージ化を開始しました。7.3の時はパッケージの作成方法から試行錯誤していたのでずいぶん苦労しましたが、経験を積んだおかげで7.4ではそれぞれのパッケージごとのビルドスクリプトもすんなり作成でき、パッケージをビルドした開発環境では大きな問題もなくX11R7.4のXサーバxserver-1.5.1を起動することができました。

しかし、別のマシンでX11R7.3のパッケージを全部削除してから新しく作ったX11R7.4のパッケージを入れてみると、xinitxtermといった重要なソフトウェアが見つからないのでXを起動できません。おかしいなぁ……、と調べてみると、FTPで公開されているX11R7.4以下のディレクトリに含まれているのは7.3からバージョンアップされたものだけで、バージョンアップされていないxinitやxtermなどは含まれていないようです。また、Xサーバのドライバ類では名称が変更されているものもあります(7.3ではIntelの統合チップセット用のドライバはxf86-video-i810だったのが、7.4では新しいビデオチップのサポートも加えてxf86-video-intelに変更された、など⁠⁠。

こうなると、X11R7.4へアップデートするには、単純に7.3のパッケージを全て削除して7.4のパッケージで置き替える、というわけには行かず、7.3から7.4で更新されたパッケージのみ入れ替えて、それ以外は残しておく、といった作業が必要になりそうです。Plamo-4.5のX関連のパッケージは300近くあるため、どれを残してどれを更新するかを一々手でチェックするのは大変な作業でしょう。このような作業にはソフトウェア・ツールズの力を借りるにかぎります。

7.3と7.4でどれくらい差があるかを調べるために、7.3のパッケージと7.4のパッケージの一覧を比較することにしました。まずはPlamo-4.5のDVDイメージを/loopディレクトリにマウントし、plamo/02_x11にあるX11R7.3由来のパッケージの一覧をR73.datというファイルに記録します。

% ls /loop/plamo/02_x11/{app,doc,driver,font,lib,proto,util,xserver}.tgz/*.tgz > R73.dat 

同様に、X11R7.4用に新規作成したパッケージの一覧も用意します。

% ls X11R74/*/*.tgz > R74.dat

この状態では、R73.datもR74.datも、ファイル名の中にパス名が含まれたままになっています。

 (R73.dat)
 /loop/plamo/02_x11/app.tgz/appres-1.0.1-i586-P1.tgz
 /loop/plamo/02_x11/app.tgz/bdftopcf-1.0.1-i586-P1.tgz
 ...

 (R74.dat)
 X11R74/app/appres-1.0.1-i586-P1.tgz
 X11R74/app/bitmap-1.0.3-i586-P1.tgz
 ...

このままでは比較できないのでパッケージ名だけを取り出すことを考えます。Linux/UNIXの場合、⁠/⁠がパスの区切りになるので、⁠/⁠を区切り記号にしてファイル名を分割すればよさそうです。このような作業にはcutコマンドが便利です。

% cut -f6 -d'/' R73.dat > R73-packages.dat
% cut -f3 -d'/' R74.dat > R74-packages.dat

 (R73-packages.dat)
 appres-1.0.1-i586-P1.tgz
 bdftopcf-1.0.1-i586-P1.tgz
 ...

 (R74-packages.dat)
 appres-1.0.1-i586-P1.tgz
 bitmap-1.0.3-i586-P1.tgz
 ...

次に、これら2つのパッケージをcatを使って合併した上でsortし、同じようなパッケージ名がまとまるようにしてみます。

% cat R73-packages.dat R74-packages.dat | sort | less
 IPAfont-00203-noarch-P3.tgz
 VLGothic-20080610-noarch-P1.tgz
 applewmproto-1.0.3-i586-P1.tgz
 applewmproto-1.0.3-i586-P1.tgz
 ...
 libAppleWM-1.0.0-i586-P1.tgz
 libAppleWM-1.0.0-i586-P1.tgz
 libFS-1.0.0-i586-P1.tgz
 libFS-1.0.1-i586-P1.tgz
 libICE-1.0.4-i586-P1.tgz
 libICE-1.0.4-i586-P1.tgz
 libSM-1.0.3-i586-P1.tgz
 libSM-1.1.0-i586-P1.tgz
 ...

両者をマージして調べてみると、X11R7.4として配布されているソースコードには7.3からバージョンアップしたものだけでなく、そのままのバージョンのものも含まれているようです。だとすると7.4に必要なものはリリースに含まれているべきだと思うのですが、GUIログイン用のディスプレイマネージャ(xdm)なども7.4のリリースには含まれていないので、別途用意しないといけないことも確かです。

ソースコードのモジュール化を進めた結果、メンテナ間の足並みが揃わなくなったことが原因のように思いますが、7.4として公開されているソースコードだけでは動作するシステムにならないので、とりあえずは動いている7.3をベースに、7.4として配布されている同名のパッケージのみを更新し、その他は残しておく、という方針にするのがよさそうです。

そういう条件で更新すべきパッケージを調べるために、パッケージのベース名のみを切り出して、uniqを使って7.3と7.4の双方に含まれるものを抽出しました。

% cat R7[34]-packages.dat | cut -f1 -d'-' | sort | uniq -c | less
      1 IPAfont
      1 VLGothic
      2 applewmproto
      2 appres
      1 bdftopcf
      1 beforelight
      2 bigreqsproto
      2 bitmap
      2 compositeproto
      2 damageproto
      2 dmxproto
      1 editres
 ...

これで頭に2が付いているのは7.3と7.4の双方に含まれているパッケージなので、これらは7.4のパッケージで安全に更新できそうです。一方、それ以外のパッケージは7.3由来か7.4由来かを区別しながら、もう少し詳しく見る必要がありそうです。

最後の部分の処理はgawkを使ってもできそうです。gawk ならば、区切り文字を示すFS変数を⁠-⁠(ハイフン)にして、それぞれのパッケージ名の連想配列に出現数をカウントする、といった処理になるでしょう。

% cat R7[34]-packages.dat | gawk 'BEGIN{FS="-"}{count[$1]++}END{for(i in count)print i, count[i]}' 
xbacklight 1
compositeproto 2
xvidtune 1
xkbcomp 2
xhost 2
xf86_video_r128 1
 ...

これくらいの処理ならばgawkのスクリプトを書くよりも基本コマンドを組み合わせて使う方が楽そうですが、この処理をより汎用的に使えるようにすれば、パッケージ名のダブリをチェックするためのツールに進化しそうです。そのようなツールはPerlやPythonで書いておく方が、メンテナンスや新機能の追加等も容易になるでしょう。

非定型な作業では、実際にやってみないとどういう処理が必要になるかがわからないことがよくあります。今回紹介した例でも、文章にすると一直線に進んでいるように見えますが、実際の作業時はシェルのヒストリー機能を使いながら、処理の手順や方法をあれこれ変えて試しています。小さなコマンドを組み合わせて複雑な処理を実現するソフトウェア・ツールズの考え方は、⁠どういう処理が有効かわからないので、いろいろな方法を試してみる」といった試行錯誤的な作業の際に力を発揮します。たいていの雑作業は、使い捨てのコマンドラインや短いシェルスクリプトで処理できますが、より多機能、高性能なスクリプト言語で書き直す際にも、ソフトウェア・ツールズを使ったプロトタイピングは有効な設計図になります。

おすすめ記事

記事・ニュース一覧