[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

工具 (tools) を組み合わせる

さて、大規模な ISP のサーバーシステムがあって、何十人ものユーザがログインしているとしよう。 経営側がシステム管理者に、ログインしているユーザのソートしたリストを生成するプログラムを書くことを求めている。 しかも、あるユーザが多重ログインをしていても、その人の名前は出力に 1 回だけ現れればよいという条件がある。

システム管理者は腰を据えてシステムのマニュアル類に取り組み、そうした作業を実行する C のプログラムを書くこともできるだろう。そのためには、たぶん数百行のコードが必要であり、 プログラムを書いて、テストして、デバッグするには、2 時間ぐらいかかるはずだ。 それに対して、ソフトウェアの道具箱に精通しているシステム管理者なら、 C のプログラムを書く代わりに、ログインしているユーザのリストを生成するところから始めることができる。

 
$ who | cut -c1-8
-| arnold
-| miriam
-| bill
-| arnold

次に、リストをソートする。

 
$ who | cut -c1-8 | sort
-| arnold
-| arnold
-| bill
-| miriam

最後に、ソートしたリストを uniq に渡して、重複を除く。

 
$ who | cut -c1-8 | sort | uniq
-| arnold
-| bill
-| miriam

実を言うと、sort コマンドには ‘-u’ というオプションがあって、uniq がやることをやってくれる。しかし、uniq にはほかの働きもあり、そちらは ‘sort -u’ で代用することができない。

システム管理者が、以下のように、このパイプラインをシェルスクリプトにしておけば、 システムのすべてのユーザが利用できるようになる (‘#’ はシステム管理者、すなわち root のプロンプトだ)。

 
# cat > /usr/local/bin/listusers
who | cut -c1-8 | sort | uniq
^D
# chmod +x /usr/local/bin/listusers

ここには、心に留めておくべき重要なことが四つある。 まず第一に、1 行のコマンドラインにたった四つのプログラムを書くことで、システム管理者は約 2 時間分の仕事をしないで済ますことができた。それだけではない。シェルのパイプラインは、 C のプログラムを使った場合と比べても、ほぼ同じくらい効率がよく、 プログラマの労働時間という点から見ると、ずっとずっと効率がよい。 人間の労働時間はコンピュータの時間よりはるかに高価であり、 「何もかもやるには、いつだって時間が足りない」現代社会では、プログラマの時間を 2 時間も節約するのは、馬鹿にできない成果だ。

二番目に、ツールを組み合わせることで、個々のプログラムの作者が想像もしなかったような、 ある特定の目的のための仕事をやってのけることができる。 これも、強調しておくべき重要なことである。

第三に、ここでやって見せたように、段階を追ってパイプラインを構成するのも有益な方法だ。 そうすれば、パイプラインの段階ごとにデータを目で見ることができるので、 ツール類を間違いなく適切に使っているという自信を得ることができる。

最後に、実行したパイプラインをシェルスクリプトにまとめておけば、他のユーザがそのコマンドを使うことができる。 彼らのために作成した手の込んだコマンドの配管工事を、彼らは憶える必要すらないのだ。 どうやって実行するかという点から見ると、シェルスクリプトもコンパイルされたプログラムも見分けが付かないのである。

ここまでは準備運動だ。続いて、もっと複雑なパイプラインをもう二つご覧に入れよう。 そのためには、工具をもう二つ紹介する必要がある。

一つ目は tr コマンドだ。“transliterate (翻字する、字を置き換える)” の意味である。 tr コマンドは、一字一字処理して行くというやり方で、 文字を置き換える (参照: tr: 文字の置換、圧縮、削除を行う)。 通常、このコマンドを使用するのは、大文字を小文字に変換するといったことのためである。

 
$ echo ThIs ExAmPlE HaS MIXED case! | tr '[:upper:]' '[:lower:]'
-| this example has mixed case!

役に立ちそうなオプションがいくつかある。

-c

リストされた文字の補集合を動作対象にする。 言い換えると、指定された集合に存在しない文字に対して操作が行われる。

-d

一つ目の集合にある文字を出力から削除する。

-s

出力中の連続する同一文字を、ただの 1 文字に圧縮する。

すぐ後で、この三つのオプションをすべて使うことになる。

紹介するもう一つのコマンドは、comm だ。 comm コマンドは、二つのソートされた入力ファイルを入力データとして受け取り、 両ファイルの各行を三つの列に分けて表示する。 出力される列は、一番目のファイルにのみ存在する行、二番目のファイルにのみ存在する行、 両方のファイルに存在する行の順番である。‘-1’, ‘-2’, ‘-3’ というコマンドライン・オプションを付けると、対応する列を表示しないようになる。 (これは直感的ではないので、ちょっとした慣れが必要だ。参照: comm: ソート済みの二つのファイルを一行づつ比較する) 例を挙げよう。

 
$ cat f1
-| 11111
-| 22222
-| 33333
-| 44444
$ cat f2
-| 00000
-| 22222
-| 33333
-| 55555
$ comm f1 f2
-|         00000
-| 11111
-|                 22222
-|                 33333
-| 44444
-|         55555

ファイル名を ‘-’ にすると、comm は通常ファイルではなく、標準入力を読み込む。

これで、気の利いたパイプラインを組み立てる準備ができた。 最初に作るアプリケーションは、単語の出現頻度カウンターである。 これは、ある特定の単語を使いすぎていないかどうか、文書の作成者が判断するとき、役に立つ。

最初のステップは、入力ファイル中のすべての文字を大文字か小文字のどちらかに統一することである。 “The” と “the” は、頻度計算にとって同じ単語だ。

 
$ tr '[:upper:]' '[:lower:]' < whats.gnu | ...

次のステップは、句読点を除去することだ。 引用符の付いている単語と付いていない単語も同じものとして扱った方がよいだろう。 それならば、句読点類をすっぱり取り除いてしまうのが、一番簡単だ。

 
$ tr '[:upper:]' '[:lower:]' < whats.gnu | tr -cd '[:alnum:]_ \n' | ...

二番目の tr コマンドは、リストされた文字の補集合を操作対象にしている。 すなわち、アルファベットのすべての文字、数字、アンダースコア、空白以外を対象にするわけだ。 ‘\n’ は改行文字のことであり、これもそのまま残さなければならない。 (実用に供するスクリプトでは、ついでに ASCII タブ文字も残した方がよいだろう。)

この時点で、空白 (訳注: 改行を含む) で区切られた単語からなるデータができていることになる。 単語には、英数字 (それにアンダースコア) しか含まれていない。 次のステップは、データをバラして、1 行 1 単語になるようにすることだ。 そうすれば、すぐ後で見るように、出現回数の計算がずっと楽になる。

 
$ tr '[:upper:]' '[:lower:]' < whats.gnu | tr -cd '[:alnum:]_ \n' |
> tr -s ' ' '\n' | ...

このコマンドは、空白を改行に変える。‘-s’ オプションが付いているので、 出力中の連続する改行文字はたった 1 個に圧縮され、空行が取り除かれることになる。 (なお、2 行目行頭の ‘>’ という記号は、シェルの二次プロンプトである。 シェルがユーザに、コマンドがまだ最後まで打ち込まれていないと知らせるとき、これが表示される。)

今や、1 行 1 単語からなるデータが手元にある。句読点は含まれず、すべて小文字だ。 これで、各単語の出現回数を数える準備が整った。

 
$ tr '[:upper:]' '[:lower:]' < whats.gnu | tr -cd '[:alnum:]_ \n' |
> tr -s ' ' '\n' | sort | uniq -c | ...

この時点で、データはたぶんこんなふうになっているだろう。

 
     60 a
      2 able
      6 about
      1 above
      2 accomplish
      1 acquire
      1 actually
      2 additional

なんと、出力が出現回数ではなく、単語によってソートされている! こちらとしては、最も頻繁に使われる単語ほど先に表示したいのにだ。 幸いなことに、それは簡単に実現できる。sort のオプションをもう二つ使うだけでよい。

-n

文字としてではなく、数値としてソートする。

-r

逆順にソートする。

最終的なパイプラインは次のようになる。

 
$ tr '[:upper:]' '[:lower:]' < whats.gnu | tr -cd '[:alnum:]_ \n' |
> tr -s ' ' '\n' | sort | uniq -c | sort -n -r
-|    156 the
-|     60 a
-|     58 to
-|     51 of
-|     51 and
…

ふう、憶えることがどっさり! うん、でもね、同じ原則を応用してるだけなんだよ。 たった 2 行、6 個のコマンドで (実際には、長い 1 行を便宜上 2 行に分割しているだけだが) 興味深く有用な作業をするプログラムが出来上がった。それも、同じこことする C のプログラムを書くよりもずっと短い時間でだ。

上記のパイプラインをちょっといじるだけで、なんと、簡単なスペルチェッカーが出来てしまう。 ある単語の綴りが正しいかどうかを判断するには、辞書で調べさえすればよい。 その単語が辞書になければ、綴りを間違えている可能性が高いわけだ。 そこで、とりあえず、辞書が必要になる。辞書の在り処は、慣例からすると ‘/usr/dict/words’ だ (現在では ‘/usr/share/dict/words’ かもしれない)。筆者の GNU/Linux システムでは (5)、それはソートされた 45,402 語からなる辞書である。

それでは、自分の作ったファイルをどうやって辞書と比べるのか? 前の例と同様、ソートした単語のリストを 1 行 1 語の形式で生成する。

 
$ tr '[:upper:]' '[:lower:]' < whats.gnu | tr -cd '[:alnum:]_ \n' |
> tr -s ' ' '\n' | sort -u | ...

必要なのは、辞書にない単語のリストだけだ。そこで、comm の出番になる。

 
$ tr '[:upper:]' '[:lower:]' < whats.gnu | tr -cd '[:alnum:]_ \n' |
> tr -s ' ' '\n' | sort -u |
> comm -23 - /usr/dict/words

-2’ と ‘-3’ のオプションを使うと、辞書 (2 番目のファイル) にしかない行と、両方のファイルにある行が排除される。1 番目のファイル (標準入力、すなわち、自分が使った単語のリストだ) にしかない行は、辞書に存在しない単語だ。 そうした単語は、綴りを間違えている可能性がかなり高いわけである。 ご覧に入れたこのパイプラインは、Unix における本格的なスペルチェッカーへの最初の一歩だったのである。

他にも一言述べておくべきツールがいくつかある。

grep

ファイルを調べて、正規表現にマッチするテキストを検索する。

wc

行数、単語数、文字数を計算する。

tee

データが流れるパイプのための T 字管。データをファイルと標準出力にコピーする。

sed

ストリーム・エディタ。上級ツール。

awk

データ処理用の言語。これも上級ツール。

ソフトウェア工具論が取り入れたものに、次のちょっとしたアドバイスもある。「骨の折れる部分は、他の奴にやらせろ」。 すなわち、ある道具を選んで、必要なことの大部分をやらせ、それから、その結果に手を加えて、こちらの望む形にする、ということである。

要約しておこう。

  1. 個々のプログラムは、一つの仕事をきちんとやってのければよい。それ以上でもそれ以下でもない。
  2. プログラムを適切な配管工事で組み合わせると、全体が部分の総和以上になる結果が生じる。 作者が想像もしなかったようなプログラムの新しい使用法が見つかることもある。
  3. プログラムは決して余計なヘッダや追加情報を出力すべきではない。 そうしたものもパイプラインの先へ送られてしまうかもしれないからだ。 (これは、これまでに言及しなかったが、重要なことだ。)
  4. 骨の折れる部分は、他の奴にやらせろ。
  5. 自分の道具箱をよく知れ! 個々のプログラムを適切に使え。適切なツールがなかったら、それを作れ。

これを執筆している時点で、ここで取り上げたプログラムはすべて次の URL から手に入れることができる。
http://ftp.gnu.org/old-gnu/textutils/textutils-1.22.tar.gz
もっと新しいバージョンは以下の場所にある。
http://ftp.gnu.org/gnu/coreutils

この記事で筆者が述べたことに、新しいことは何もない。 ソフトウェアは工具だという思想が最初に紹介されたのは、Brian Kernighan と P.J. Plauger による Software Tools という本の中だった (Addison-Wesley, ISBN 0-201-03669-X)。ソフトウェア工具の書き方と使い方を教えるこの本は、1976 年に執筆され、ratfor (RATional FORtran) という名前の FORTRAN のプリプロセッサを使用している。その当時、C は今ほどありふれてはいず、FORTRAN がそうだったのだ。最後の章では、ratfor を FORTRAN に変換するプロセッサを ratfor で書いて見せている。ratfor は C にとてもよく似ているので、 C を御存じの方なら、コードを追うのに何の苦労もないことだろう。 (訳注: Software Tools の翻訳は「ソフトウェア作法」という題で 1981 年に出版されている。木村泉 訳、共立出版)

1981 年に本は改訂され、Software Tools in Pascal という形でも手に入るようになった (Addison-Wesley, ISBN 0-201-10342-7)。 どちらの本も現在でも入手可能であり、プログラマなら、一読の価値が十分にある。 この 2 冊の本が筆者のプログラミングに対する見方を大きく変えてくれたことに、疑いの余地はない。

両方の本にあるプログラムは、Brian Kernighan のホームページから手に入れることができる (http://cm.bell-labs.com/who/bwk)。Software Tools Users Group という活動的なグループが長年に渡って存在し、そのメンバーがオリジナルの ratfor プログラムを、FORTRAN コンパイラを持っているほとんどすべてのコンピュータ・システムに移植していた。 だが、1980 年代の中頃に Unix が大学を越えて浸透し出すにつれて、グループの人気は衰えて行った。

現在では GNU のコードをはじめ、Unix クローンのプログラムがどんどん作られており、 上記のプログラムはほとんど関心を持たれていない。 それに、現代の C のバージョンの方がはるかに効率がよく、できることも上記のプログラムよりずっと多くなっている。 それでも、よいプログラミング・スタイルのお手本として、 また、今でも価値がある考え方を熱心に説いている点において、 この 2 冊の本は肩を並べるものがない。筆者としては、大いにお薦めする次第だ。

謝辞: ソフトウェア工具の最初の道具鍛冶である、Bell 研究所の Brian Kernighan 氏に、この記事を読んでチェックしてくださったことについて、 心からお礼を申し上げる。


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

This document was generated on June 7, 2022 using texi2html 1.82.