UNIXのバックグラウンド

Unix に初めて接する方々が、それを理解し、応用し、そのマニュアルをご覧にな るときの手助けとして、システム全体の背景を解説してみようと思います。

「商品」と「拡張」部分を割り引きした unix は驚くほど効率的で居心地の良いシ ステムですが、派手でもなく、素人受けするものでもなく、馴れるに従って離れ難 くなり、見た瞬間でなく、後になって深い感動を覚えるといったところがあります。 よく言われているような、ソフトウェア開発といった狭い領域だけでなく、個人か ら組織、日常業務から非定型業務、事務、技術、研究開発、出版、自動化等、あら ゆる分野で、その威力を発揮します。

日頃、目につくことは少ないのですが、実際、日本でも、一部上場会社から、家族 経営の商店、個人の自宅と、幅広いところで、汎用機やオフィスコンピュータ、そ の他のシステムをはるかにしのぐ活躍をしています。いずれも、オーソドックスな ミニコンピュータとオーソドックスな Unix システムで、それらは、コストパフォ ーマンスとチームで仕事をするときの心地よさで選ばれました。本来の Unix の特 徴を生かして、開発から運用まで、すべてユーザが自分でやっていますから、マス コミや業界関係者にも内容がよくわからないようです。また、「Windows」や「EWS」 のようなお金を使うためのOSではありませんから、業界関係者には営業的魅力に 欠けます。Unix はお金を使わないOS、利益を生むためのOSなのです。

この本来の Unix (good old unix ?) を素直に無駄のない姿で使う、あるいは理解 しようとしたとき、最も本質的な点は、次の3つではないかと思います。

  一貫した「テキストファイル」の採用
  それを決定的にした、「正規表現」
  パイプラインの並列処理を足場にした、「コマンドの直交性」
これらは、unix が生まれ育った時代に開発された多くの技術から、深い洞察力と 将来に対する見通しに基づいて選択されたもので、この「選択」こそ unix のすべ てであったような気がします。

以下、初期の Unix で実現され、未だにその跡をとどめる「Small is beautiful」 の世界、Unix の本質的な部分と、その応用ための着眼点に触れてみたいと思いま す。

unix のファイル構造

テキストファイル

Unix の最も重要な基盤が、一貫したテキストファイルの採用にあり、awk, cat, cp, echo, ed, grep, lp, ls, mail, man, pg (more), pr, sort, spell, vi, wc 等、UNIXの圧倒的な量のコマンドがテキストファイルを対象にしています。は っとするほどのことではないと思われる方もいらっしゃるかと思いますが、この点 は、いくら強調してもしすぎることはないでしょう。多分、cobol のファイルのよ うに、メモリ内のイメージをそのままディスクに書きだすというのが、最も自然な 発想だろうと思います。入出力だけを見る限り、無駄もありません。unix 以外のシ ステムでは、バイナリファイルが普通で、テキストファイルは例外です。つまり、 メモリ内のデータイメージと、外部記憶のファイルが一致しているのが普通です。

これに対して、Unix のファイルはテキストファイルが基本で、バイナリファイル が例外です。ユーザが作るファイルだけでなく、Unix のシステムファイルにも、 他のシステムに見られるような、バイナリのデータファイルがほとんどありません。 /etc/utmp、/usr/adm/wtmp 等、システムが使用し、変更が不用で、他の汎用コマ ンドが直接参照する必要のない、ごく一部のファイルを除いて、ほとんどすべての データファイルがテキストファイル、つまり、文字データのファイルになっていま す。これが、他のシステムにない、Unix の類希な柔軟性を生みだす基盤のひとつ になりました。

テキストファイルの特徴は、エディタを含むすべてのプログラムで共用できること にあります。例えば、オフィスコンピュータや汎用機などの事務処理システムでは、 管理すべきデータを納めたマスタファイルを作成・維持するプログラム、変更すべ きデータを納めたトランザクションファイルを作成するプログラム、この二つを用 いてデータを更新するプログラム等、すべてが専用プログラムと専用ファイルにな り、他の汎用プログラムで直接アクセスされることはありません。そのため、会計 処理や在庫管理、給与計算といった、比較的単純な業務でも、そのプログラムの本 数はかなりの量にのぼり、なかには、その本数を誇る人さえいます。

ところが、Unix でこれらの仕事をすると、必要なプログラムやファイルが、極端 に少なくなるのです。マスタやトランザクションファイルの作成や修正も含めて、 馴染みのエディタで処理することが可能ですし、実際そうすることが日常的です。 もちろん、データのチェックや省力化を目的にした、専用の入力プログラムを作る ことも多いのですが、量の少ない仕事では、エディタや、短い awk, sh のプログ ラムで作成したほうが無駄がありません。

そして、チェックプログラムやデータ処理プログラムそのものも、わざわざc でコ ーディングすることは少なく、ほとんどは、awk, join, sort, cut, paste といっ た、既存のツールで間に合いますから、専門のプログラマが必要になることは少な く、たいていは、その業務の担当者が自分で用意しています。そして、この「自分 でやれる」という性格が、システムの内容とその運用に、驚くべき効率と柔軟性を 与えるのです。

仕事というのは、その組織と時代によって驚くほど違いますし、絶えず、その時々 の環境に適応することが要求されます。日常の仕事のかなりの部分が、利用者その ものによって自由に作れるというのは、きわめて重要です。

このように、「すべてのファイルに同じコマンド、すなわちプログラムが使える」 というのが、Unix の特徴であり、利点です。また、リダイレクトやパイプライン といった、Unix の有名な機能も、これを抜きにして成立ち得ないのです。

以下、文章のような非晶質の、構造のはっきりしないファイルでなく、管理と検索 を目的とした、構造化されたデータファイルを考えます。構造化されていないファ イルについては、この応用ないしは、極限として、理解したり、対処することが可 能です。

可変長ファイル

データファイルは、レコードの集合からできています。レコード record というの は、ある特定な品目の売上データ、入庫データ、あるいは、ある個人の給与支払い データ等、ひとつの対象に付随したデータのセットです。これが、ファイルのいち ばん大きな、繰り返し単位になり、ファイル処理は、この繰り返しに対応します。 例えば、awk の場合は、while や for により、ループを指示することなく、もと もと、この繰り返しを行なうようにできていますし、grep, join, cut, paste, sort 等、他のデータ処理を目的としたコマンドも、レコードセパレータが改行文字 に固定されていること以外は、同じです。

次に、このレコードは、フィールド field と呼ばれる、個々のデータから構成され ます。先の例では、品名を表わすコード、数量、日付、相手先、金額といったもの がフィールドで、これらの内容や性格は様々ですから、繰り返しで対処するのは無 理で、個別に扱うことになります。awk の場合は、ユーザのプログラミングにより、 この個別性に対処し、他のコマンドでは、コマンドそのものの選択と、そのオプシ ョンで、対処します。オプションというのは、そのコマンドの枠内での、ユーザの 選択ですが、フラグ flag とか、スイッチと呼ばれることもあります。

さて、オフィスコンピュータや汎用機が扱うファイルは、一般に、固定長ファイル です。例えば、次のデータは、全銀(全国銀行協会)形式のレコードの一例です。 1行に入りきれませんから、行が分かれていますが、空白も含めて、すべての文字 がべたっとつながっています。文字コードは、EBCDIC という、IBM 社固有のコード が普通ですが、パーソナルコンピュータの普及により、一部、シフトJISも使わ れています。

10310107240107240107240005コバヤシ          132ミヤマエ           \
00010004306591                                        1 00000153794630
個々の項目、すなわちフィールドは、一定の桁数で構成されていて、フィールドの 桁数とレコードの大きさがすべてのレコードについて同じ値になり、これが、この 分野で使われている主力言語、cobol のレコード定義に反映しています。つまり、 フィールドやレコードの切れ目を表わす、セパレータ separator がありません。 人が見るのはたいへんですが、プログラムで扱うのは簡単です。名前や住所等、本 来可変長のフィールドは、予想される最大幅にせざるを得ず、多くの無駄とシステ ム設計の悩みを生みだすことになります。

一方、Unix のファイルは、原則として可変長です。フィールドの分離は、桁数でな く、フィールドセパレータとレコードセパレータで行なわれます。つまり、ファイ ルの構造は、次のとおりです。

  フィールド{fs}フィールド{fs} .. {rs}
  フィールド{fs}フィールド{fs} .. {rs}
  ..
フィールドセパレータ {fs} はフィールドの切れ目を表わす文字で、通常、空白文 字か、コロン (:) が使われます。空白文字というのは、表示の空白を作るのに使 われるスペースとタブ、改行文字のいずれかです。タブよりスペースが普通です。 住所、名前、コメント等、フィールドの情報として空白が必要な場合にコロンを使 い、そうでないとき空白という流儀が多いようです。

レコードセパレータ {rs} は、ほとんどの場合、改行文字が使われます。つまり、 1行1レコードの形式になります。希に、フィールドセパレータに改行文字を使用 して、空白行をレコードセパレータとすることもあり、awk コマンドでは、このマ ルチライン形式もサポートしています。すなわち、

  RS = ""
と、レコードセパレータに空文字列をセットしたとき、この型のレコードを扱うこ とができます。具体的には、次のようなレコード構造です。
  フィールド フィールド ..
  フィールド フィールド ..
  ..
  フィールド フィールド ..
  フィールド フィールド ..
  ..
空白行までが、1件分のレコードです。

人が直接見るためのファイル、例えば、電話帳とか住所録等は、この形式によく馴 染みます。ただし、awk 以外のコマンドのほとんどが、この形式をサポートしてい ませんから、どうしても1行1レコードでうまくゆかないときに限るべきです。こ の種のマルチライン形式のファイルを扱う grep を作ることは容易ですが、商品と してのUNIXでなく、利用者のUNIXでは、「なにが減らせるか」が基本で、 よくよく考えてからにしなければなりません。私たちが「追加したい。貢献したい」 と考えたことは、すでにずっと昔、天才達により、捨てられていたのかもしれませ ん。コンピュータ科学や Unix には、既にそれだけの歴史があります。

フィールドをさらにサブフィールドに分解すると、きれいな構造になることがあり ますが、awk でこの形式を扱うときは、split() 関数を使うと便利です。awk の

  split(文字列, 配列 [, セパレータ])
は、文字列をレコードとみなして、配列にフィールド分解し、フィールド数を返し ます。

さて、先ほどの固定長レコードを、典型的な Unix のファイルに変換すると、次の ようになります。

1:3:1:10724:10724:10724:5:コバヤシ:132:ミヤマエ:0:1:4306591::1::153794630
かなり、見やすく、しかも短くなるのががわかると思います。例えば、銀行から金融 機関のコード表をもらってきて、Unix 流の可変長レコードに変換すると、ほぼ25% 程度に圧縮されてしまいます。Unix の場合、いかに少ないディスクスペースで間に 合うかが、よくわかる事例のひとつです。

可変長のデータ形式は、プログラムで扱うのがすこし面倒です。しかし、一度苦労 しておけばよいことですし、変換等に要する時間は入出力の時間に比べて充分小さ く、プログラムのサイズも、問題にならない大きさです。

Unix の場合は、ほとんどすべてのコマンドが、この可変長データ形式を想定して いることに、注意してください。awk, cut, join, paste, sort, tr, grep といっ た、データ処理を目的としたコマンドから sh に至るまで、すべてこのファイル構 造をあてにしています。ですから、このようなデータ構造、すなわち、レコード、 フィールド、フィールドセパレータといった概念をはっきり意識していないと、う まく使えません。

この点は、c についても同様で、標準ライブラリの scanf() 等の入力関数も、こ のデータ構造を前提にしています。他の言語の処理系の入出力関数と比較してみる と、Unix c のライブラリには、文字列と、上記の可変長レコード形式を想定した 関数の多いことがよく解ると思います。

管理

ところで、このデータファイルの目的は、管理と検索にあります。「管理というの は、残と合計の管理である」と言ってしまってよいほど、この二つがが本質的で、 それに対象識別用のキー(コード)、その他の検索情報が加わって、管理の対象、 すなわち、マスタファイルが構成されます。例えば、在庫のマスタファイルであれ ば、その典型的なレコードは、次のようになります。

  コード:入庫合計:出庫合計:残高:単価:コメント
この合計と残を更新することが管理であり、コードやコメントの変更がメンテナン スです。

一定時間おきに処理が行なわれる、いわゆるバッチ処理の場合は、これらのすべて のフィールドを可変長にすることができます。リアルタイムで更新が行なわれる場 合は、固定長のフィールドを用意せざるをえませんが、その場合でも、管理の対象 となる合計と残だけですみますから、cobol の使用を前提にしたファィル構造にく らべると、コンパクトになります。

残は時間軸上の横断面データ、合計は変化を表わす時系列データであることに注意 してください。この二種類のデータは、相補的なもので、前者は現状、後者は歴史 ないしは経過になります。例えば、会計の分野では、前者が貸借対照表、後者が損 益計算書です。貸借対照表は、ある時点に於ける正負を含めた財産の残を、その出 所と使い途であらわします。左側、つまり専門家のいう借方 debit がお金の使い 道、右側の貸方 credit と呼ばれる方がその出所で、この点に気づけば、会計学の 本質的な部分は容易に理解できます。専門用語は、えてして、苦労あるいは参入障 壁を高めるために存在します。

さて、管理の目的となるファイルがマスタファイル master file ですが、これを 更新するためのデータファイルは、トランザクションファイル transaction file と呼ばれます。ジャーナル journal と呼ぶ人もいます。今の在庫の例ですと、例 えば、

  コード:入庫合計:コメント
とか、
  コード:データ区分:数量:コメント
といった構造になります。前者は、入庫や出庫のデータ発生源が別で、別々に更新 処理を行なう場合、後者はすべての変化を同時に処理する場合で、「データ区分」 のフィールドには、入庫、出庫などのデータの区別をあらわす数字や文字をいれま す。文字にするか数字にするかは、可読性と入力効率とのバランスで決めます。毎 日たくさんの処理が行なわれますから、テンキーだけでまにあう、あるいは素人が キーを探す必要のない、数字が多いようです。キーもだめということになると、バ ーコード等の自動入力になります。

これら二つのファイルから新しいマスタファイルを作りだすのが、更新 update 処 理と呼ばれる仕事ですが、Unix 場合は、それがバッチ処理で行なわれるのであれ ば、通常よく現われる、小規模のデータの場合、awk のみ、あるいは sort, join, awk の組合せで、充分対処可能です。希に、cut や paste も使われます。例えば、 今の例でしたら、トランザクションファイルのコードをキーにして sort し、join で結合して、awk で新しいマスタファイルを作成するといった方法等があります。 また、独自の、優れたツールを自作して動かしている例もあります。

バッチ処理というのは、一定時間おきにデータを batch にして(まとめて)、投入 する手法です。即応性に乏しいのが難点ですが、効率が良く、無人化が容易なのが 長所です。リアルタイムで更新するのが最高と思いこんでいる方もいらっしゃいま すが、バッチでできることはバッチでするのが原則です。システムとしてリアルタ イムのつもりでも、運用あるいは現実としてバッチ処理になっていることは多いも のです。

sh の対話機能の不足を悲しがる人の多くは、このバッチ処理、つまりプログラミン グの有効性を意識していないような気がします。Unix にとって、sh は本質的なも ののひとつです。コンピュータは、本来、人手を省くためにあります。対話型の処 理は、人間が相手をすることが前提になっていることを忘れてはなりません。これ は、エデイタについても同様で、emacs や vi のようなスクリーンエディタは、視 覚的なインタフェースがよい反面、キータッチが増え、検索やグローバルな処理が 遅くなります。

データファイルのほとんどの処理は、正規表現を生かした、ed の方が速いですし、 多くの端末をサポートするマルチユーザシステムでも、負荷が大きくなりません。 また、ed だと、ヒアドキュメント等を使った自動化も可能で、例えば、次のような sh スクリプトがよく使われます。

  ed $1 $2 <<!
  g/^\.fax/s/[0-9][0-9\-]*/$2/
  w
  q
  !
これは、$1 すなわち最初のアーギュメントで指定された、ファイル中のすべての
  .fax
で始まる行の、数字とハイフンとコンマで書かれた、電話番号を、二つ目のアーギ ュメントで指定された電話番号に書換えた後、書き出して、終了するものですが、 普通は、もっといろいろなことをやります。定型的な文書に決まった処理を行なう のであれば、その操作は ed で自動化できます。簡単な処理でしたら、ed より、 sed の方が速いし、一時ファイルも不用ですが、ed は分かりやすいのが利点です。

emacs 型のスクリーンエディタと、ed 型のラインエディタの一長一短の性格は、 とても面白いと思います。roff を使わずに、人手でフォーマットしながら文書を つくったり、文章の推敲等の用途では、emacs 型のエディタに圧倒的な利点があり ます。ということは、この種の仕事は、個人用のマシンですべきであるということ かもしれません。

なお、ヒアドキュメントの中で、文字としての $ を使うときは、\$ と引用しなけ ればならないことに、注意してください。うっかりすると、よく、考え込んでしま います。

マスタファイルそのものの変更、つまり、残と合計以外のフィールド変更、レコー ドそのものの追加、削除は、メンテナンス maintenance あるいは変更処理と呼ば れ、更新とは別なプロセス、別な管理者によって行なうのが普通です。同時にやる とエラー検出力が低下し、大きな誤りが出やすくなります。これは、プログラム技 術でなく、システム設計の問題です。

さて、管理の次は、検索と認識です。

正規表現

UNIXのテキストファイルを決定的にしたのが、一貫した正規表現の採用で、こ れが ed, grep, sed, sh, egrep, awk, lex と、Unix の重要なツールの核心にな っています。例えば、電話帳の検索、会計データの検査、技術情報の検索、過去の 記録の抽出と、大きなデータファイルから、特定な文字パターンを検索して、何ら かのアクションを起こすというのは、きわめて日常的な仕事ですが、大きな苦痛を ともないますし、間違いの起き易い仕事でもあります。しかし、この機械化は、従 来のシステムでは困難でした。Unix の場合は、正規表現の採用により、驚くほど 自然な処理を可能にしています。例えば、awk は、この検索とアクションを、正規 表現によるパターン認識と、認識時のアクションという構文に還元しています。

  /パターン/ { アクション }
がそれです。このアクションをデフォルトの print にすると、grep になります。 awk の短いプログラムで、Unix のかなりのコマンドを実現することができますが、 専用のコマンドに比べて遅く、プロセスのイメージも大きくなります。

正規表現 regular expressio というのは、文字パターンを認識するためのきわめ て有効な手法です。人にとって考え易い、ごく小数の規則で、広範囲の文字パター ンを効率よく表現することができ、しかも、そのパターンを自動認識するための仕 掛、つまり遷移図あるいは有限オートマトンと呼ばれる認識機構を実現するプログ ラムに、プログラムによる自動変換が可能というのが、その本質になります。lex というコマンドは、この正規表現について、プログラムによるプログラムのための 自動変換を行ないます。

文字パターンを認識するために、人はまず、負担の少ない正規表現を書き下ろし、 くたびれる有限オートマトンへの変換、つまり認識結果の分類と分類規則の作成、 そしてその規則の実行を、コンピュータにさせるというのが、正規表現の意味です。 awk, grep といったコマンドは、すべて、正規表現を有限オートマトンに変換し、 それを実行するメカニズムを含んでいます。

正規表現は、英語のアルファベットや日本語の文字等、その言語の基盤とな文字セ ット alphabet を前提にして考えますが、その規則は、例えば、次のような、まこ とにそっけないものです。

1) 空文字(空集合)は正規表現である。
2) 文字セットの任意の文字も正規表現である。
3) 正規表現の和(二つの正規表現のいずれかという論理和)、二つの
	正規表現の積(連接 concatination)、正規表現の任意個の繰り返し
	(閉包)も正規表現である。
閉包 closure 、つまり繰り返しが再帰性そのものであることに注意してください。 それが、この概念の価値です。Unix のディレクトリ構造、家系図、フラクタル図形 等、再帰性はいたるところに現れ、大きな役割を果たします。再帰性については、 昔、
  D.W.Barron,- RECURSIVE TECHNIQUES IN PROGRAMMING
	(Macdonald:London)
にたいへん感動しました。いまなら、
  J.S.ロール- プログラマのための Pascal による再帰法テクニック
	(啓学出版)
といったところでしょうか。なぜか、再帰には、人をわくわくさせるところがあり ます。

Unix のコマンドでは、これら正規表現の規則を表わすのに、次のような記号を用 います。

--------------------------------------------------------------------------
c		文字 c (., ^, $, [, (, *, +, ? 等の特殊文字以外)
\c		文字としての c (例えば、\[ は [、\\ は \ そのものを表わす)
.		任意の1文字
^		行の先頭
$		行の終わり
[uv..]		u, v, .. のいずれか1文字
[^uv..]		u, v, .. 以外のいずれか1文字
[u-w]		u から w までの範囲の、いずれか1文字
[^u-w]		u から w までの範囲にない、いずれか1文字
\(r\)		正規表現 r を記憶、ed, sed, test で使用
\n		n (0<=n<=9) 番目に記憶した正規表現、ed, sed, test で使用
\{m\}		直前の文字が m 個続くパターン
\{m,\}		直前の文字が m 個以上続くパターン
\{m,n\}		直前の文字が m 個以上、n 個以下続くパターン
r|s		正規表現 r か s のいずれか(和)
(r)(s)		正規表現 r のあとに s が続いたもの(r と s の連接)
(r)*		正規表現 r の0個以上の繰り返し
(r)+		正規表現 r の1個以上の繰り返し
(r)?		正規表現 r の0または1個
(r)		正規表現 r
--------------------------------------------------------------------------
前記の規則以上にいろいろありますが、先のレコード構造や、自然言語の性格を反 映した拡張です。全体としては、
  アルファベットの要素を制限するための文字セット	[ ], [^ ]
  レコード上の位置を限定するための位置指定	^, $
  アルファベットの文字を一般化するための		.
  和(論理 or)を表わすための			|
  繰り返し(再帰)を表わすための			*, +, ?, \{ \}
  優先順位を指示する				( )
  正規表現の概念表示に使ってしまった文字の引用	\c
  記憶のための					\( \)
といった理解でよいと思います。積、すなわち、連接の記号はありません。続けて 書けば、連接になります。awk のリストもこの流儀を受け継いでいて、"a b c" と いう変数のリストを表示する、
	print a b c
は、a, b, c の文字表現を連接した結果を出力しますし、
	print a, b, c
なら、コンマの位置に、OFS (出力フィールドセパレータ)を挿入します。awk は、 すべての変数の内容を、数値 (double) と、文字列データの二重に持っていて、数 値として理解できる文字列は、数値化しておきます。print 文で数値を出力すると きは、個々の項目について、OFMT(出力フォーマット)をフォーマットに指定した、 printf 文を実行します。

このメカニズムは、awk 特有の使いやすさ、つまり文字の数値を区別しない、デー タの一様性を生みましたが、それがまた、曖昧なデータに対処するために、

  a ""	# a の内容を文字列に強制
  a + 0	# a の内容を数値に強制
といった技巧を要求されることにもなりました。

., ^, $, [, (, *, +, ? 等、本来の文字として使われずに、規則を表わすために 使われる特殊文字は、メタキャラクタ meta character と呼ばれています。これら の文字を本来の文字として引用する場合は、直前に \ をつけなければなりません。 いわゆる、エスケープシーケンスです。エスケープシーケンスは、文字の本来の意 味を変えるための、遷移規則です。\ 文字そのものは、\\ で引用できます。

これらの記号は Unix のコマンドのすべてに共通というわけではありません。例外 の sh を別にして、ed, grep, sed の系統と、egrep, awk, lex の系統の二つがあ ります。前者の場合は、|, ( ), +, ? がありません。歴史的な事情もありますが、 ed の場合は、メタキャラクタをあまり増やすと、かえって不便になります。grep の正規表現は、ed とまったく同じです。もともと、ed の

  g/re/p		(re は正規表現 regular expression の意味です)
という頻繁に使うコマンド、ファイルの全行 g(lobal) について、指定された正規 表現とマッチする行を検索して表示 p(rint) する操作を、別なコマンドに分離し たもので、これら二つのプログラムのうち正規表現を扱うプログラムは、
  /usr/include/regexp.h
として、バイナリライセンスのユーザにもソースを公開、利用可能にしています。 この解説は、regexp(7) にありますが、実際使ってみたり、使っているプログラム のソースを見ないと、わかりにくいかもしれません。

ソースでないと、表現できないことが多いものです。ソースコードには、そのプロ グラマの思想、鍛錬、発想、経験、語り口、苦労、愛情といった、いろいろなもの が反映します。プログラマがプログラマに語りかけるには、ソースがいちばんだと 思います。しかし、一般公開というのは、露出症でもない限り、気がすすまないか もしれません。ソースコード付きのソフトウェアというのは、いろいろな意味で、 貴重な存在です。

egrep, awk, lex の正規表現は、前記の表のほとんど含みます。egrep を発展させ た結果のひとつが awk として残ったらしく、正規表現に関する部分は同じでし、 その痕跡があちこちにあります。egrep は、grep では手に負えない複雑な文字パタ ーンを扱えます。例えば、ユーザのログインディレクトリにある calendar という ファイルから、向こう2,3日の日付を含む行を抽出して表示したり、メイルボッ クスにいれてくれる、calendar という便利なコマンドがありますが、これは egrep を使っています。sh スクリプトですから、バイナリライセンスのユーザでも見られ ますので、ご覧になってみてください。自分で自分のプログラムを作り出す eval の使い方とか、ed の \(re\) の使い方等、いろいろ勉強になっておもしろいと思い ます。

その、Unix の calendar コマンドは、日付のパターンを作り出す仕事だけ、 /usr/lib/calprog という c のプログラムで行なっています。例えば、今日の場合、 次のようなパターンが出力されました。

  (^|[ 	(,;])(([Mm]ay[^ ]* *|0*5/|\*/)0*13)([^0123456789]|$)
  (^|[ 	(,;])(([Mm]ay[^ ]* *|0*5/|\*/)0*14)([^0123456789]|$)
最初の行が今日の日付、次が明日です。最初の正規表現のグループ、
  (^|[ 	(,;])
は、行の先頭か、空白、タブ、左括弧、コンマ、セミコロンのいずれかという意味 で、印刷すると、タブが空白の連続に見えますので、注意してください。[ ] の中 は、空白文字とタブ文字がひとつづつ入っています。これは、連続した文字や数字 の途中が、偶然月名と見られないようにするためのガード guard です。

次は、May か may の後ろに空白以外の文字が0個以上つながったあとに、空白文字 が0個以上つながった状態、あるいは 5/, 05/、*/ のいずれかの後に 13 の数字が 続くパターンの指定で、これが日付になります。[Mm]ay の後の [^ ]* は、Feb, Feb., Februaly といった多様なパターンに対応するためです。*/ は、毎月を実現 するための仕掛で、経理課や人事課等、毎月同じ仕事を計画的に行なっているユー ザの calendar ファイルに見られます。

そして、最後の正規表現は、その後が数字以外か、行の終わりでなければないこと を指示し、これも、最初の正規表現と同様に、長い文字列の途中が、偶然日付に見 える現象への対策です。正規表現により、少ない文字数で、多様な日付表現に対応 していることに、注意してください。

calendar コマンドでは、このパターンが /bin/calendar の sh スクリプトによっ て、egrep に渡されます。その結果、利用者は、ほとんど日常のメモに近い、自由 な形式で、自分の日程表を作ることができます。なお、calendar コマンドは、

  calendar -
の形式で、/etc/rc で起動時に動かしたり、連続運転の場合は、/usr/lib/crontab に登録して、深夜等に起動するのが普通です。こうすると、全員のログインディレ クトリの calendar ファイルを調べて、結果を一人一人に mail してくれますから、 朝の出勤時に login すると、「You have mail」の表示がでてきます。

つぎに、浮動小数点表示の数値を考えてみましょう。正規表現を使えば、例えば、 次のようになります。

  ([0-9]+(\.?)[0-9]*|\.[0-9]+)((e|E)(\+|-)?[0-9]+)?
面倒な例ばかりですが、これと同じ仕事をする c のプログラムと比較してみてくだ さい。いかにコンパクトで楽か、実感できると思います。浮動小数点演算を扱う言 語であれば、そのレキシカルアナライザ(字句認識部)の一部には、このパターン を認識する仕掛があります。例えば、c のコンパイラでしたら、レキシカルアナラ イザの浮動小数点の定数を認識する部分とか、scanf() や atof() のライブラリル ーチンです。PC-Unix のソースなどにもありますから、はじめての方は、一度見て おいてください。c のプログラムでは、通常、認識の状態 state は、関数の中の コーディングの位置とか、状態変数の値になります。

lex というプログラムは、このような正規表現を入力して、認識に必要な cのプロ グラムを自動生成します。人手で作る、パターンの特徴を生かしたものと比べると、 プログラムも大きく、能率も低下しますが、64KB+64KBの比較的小さなア ドレス空間で、awk や pic といった強力なプログラムを作れるだけの実力を備えて います。lex や yacc といったツールは、コンパイラ作成者の道具という印象を持 たれている方も多いと思いますが、決してそうではありません。

どの分野の仕事も、その道の言語を必要とするものですし、実際、専用の言語が使 われています。例えば、経理や営業担当者の仕事として、取引先の支払い条件をフ ァイルのレコードの一部に記述することを考えてみてください。プログラム、ある いは既存のコマンドで、うまく処理できるようにしなければならないといたら、日 常語の表現でうまくゆかないことは明かです。多分、日常語ですべての可能性を分 類、説明してみせるのも、ひと仕事です。

専用の言語と、その処理系が必要なことは多いものです。この種の言語の設計や処 理系の作成は、プログラマやシステムエンジニアより、実務の担当者のほうが適し ています。そして、コンパイラ等の言語処理技術も、もはや一部の特定な人々のも のではなくなりました。言語処理は、決して汎用のプログラミング言語だけでない ことに、注意してください。

自分が直面した問題を既存の言語によるプログラミングで解くだけでなく、その分 野に適した、よい言語を設計し、その処理系を作ることにより、驚くほど効果をあ げることがあります。これらの言語や処理系は、通常、そのユーザに固有なもので すが、一般的な例としては、Unix の文書処理に見られる、grap, pic, tbl, roff の言語があります。この種の処理系が容易に作れるのは、Unix の大きな利点です。 この種の、little language、あるいは small language を自分のために自作する のは、すこし Unix に馴れてきたユーザの大きな楽しみのひとつです。

以上、どちらかというと複雑なパターンを例示しましたが、日常必要になるものは、 もっと単純なものがほとんどですし、そうなるようにシステムを設計します。汎用 機やオフィスコンピュータの会計システムで、ふだん管理していない情報を過去数 年分のファイルから拾ってくるのは、至難の技ですが、Unix で運用しているシステ ムであればいとも簡単です。Unix は、コンピュータの初心者から、上級者にいた る、広い範囲のユーザに対応することができます。

正規表現そのものは、もともと形式言語理論とか、コンパイラ等の言語処理系の文 字パターンの認識を目的に開発されましたので、その方面の文献をみないと、ちゃ んとした解説がありません。例えば、

  A.V.エイホ・J.D.ウルマン,- コンパイラ
	(培風館)
をごらんください。すばらしい本ですが、ある程度の覚悟が必要です。原著は、大 幅に増補されています。OSの解説書としては、奇跡的明快さで知られる、
  Andrew S. Tanenbaum,- OPERATING SYSTEMS DESIGN AND IMPLEMENTAION
	(Prentice-Hall International)
のような本がご希望でしたら、
  Allen I. Holub,- COMPILER DESIGN IN C
	(Prentice-Hall) ISBN: 0-13-155045-4
がぴったりです。

Unix の場合は、このどちらかというと、コンパイラ等の言語作成者のツールだっ た正規表現を、一般利用者に解放したのが偉大な洞察だったように思えます。 Unix の場合、正規表現に慣れ、正規表現で考えるようになることが、きわめて重 要です。

最後に、sh の正規表現は、いままでの2系統とは、また少し違っていて、

-----------------------------------------
文字セット	[ .. ], [! .. ]
任意の1文字	?
任意の文字列	*	(空文字列を含む)
-----------------------------------------
と、簡単です。これは、case 文のパターンで使うとき以外は、常に、ファイルシス テムのファイル名に対して適用されることに注意してください。sh 以外のコマンド はファイル内のデータあるいはレコードを扱いますが、sh が認識すべき対象のほと んどは、ファイルやディレクトリの「名前」です。これもまた、テキストであり、 いくつかの名前の集合は、レコードを構成することに注意してください。一貫性と 一様性は、Unix の大きな特徴です。

コマンドの直交性

パイプライン

あるコマンドの出力を別なコマンドの入力に穴あきパイプで接続して、その中にデ ータを流すという、Unix のパイプラインはなんともすばらしい発想ですが、その 本質は、並列処理にあります。つまり、いくつかのコマンドが順番にではなく、同 時に動作して、共同でひとつのまとまった仕事を成し遂げるというところです。例 えば、典型的な文書処理で、

  pic file | eqn | tbl | troff
の場合、MS-DOS のようなシングルタスクの OS では、
  pic <file >tmp1	# 図形を troff のコマンドに変換
  eqn <tmp1 >tmp2		# 数式を troff のコマンドに変換
  tbl <tmp2 >tmp1		# 表を troff のコマンドに変換
  troff >tmp1		# troff の言語を写植機のコマンドに変換
  del tmp1		# 後始末
  del tmp2
と逐次処理を行なうしかありませんが、Unix のパイプラインは、すべてが同時に 働きます。つまり、処理のイメージとしては、
  pic file -> eqn -> tbl -> troff
という、順次、直列処理でなく、
          +- pic --->-+
          |           |
          +- eqn --->-+
  file ->-+           +-> タイプセッタのデータ
          +- tbl --->-+
          |           |
          +- troff ->-+
という、同時、並列処理の感覚です。

パイプラインで結合されたコマンドは、パイプラインのメカニズムによって、デー タの受渡しと、処理の同期がとられるわけですが、処理の順序という位相幾何学的 な特性を保存するための同期による遅延以外は、あくまでも同時実行です。シーケ ンシャルな処理と違って、原理的に、分割による遅延を生じないことに注意してく ださい。この分散処理の性格こそ、Unix 特有のコマンドの直交性を生む基盤です。 例えば、よく現われる、

  awk '
  /パターン/ {
  	処理
  }' ファイル
等を、
  egrep パターン ファイル | awk '
  {
  	処理
  }'
等に分解して、time 等のコマンドにより、実行時間とかCPUの負荷を調べてみる と、この性格がよく分かると思います。

パイプラインは、データをふるいにかけるという、フィルタの概念を生みましたが、 フィルタのシーケンシャルな処理だけでなく、並列処理が行なわれることが、より 本質的です。これが、「複雑で多様な現実の作業を、小数の洗練されたコマンドに 分解する」という、最も Unix らしい手法を生むことになりました。以下、このコ マンド分解の話です。

コマンドの直交性

直交性 orthogonal というのは、デカルト座標の x-y 軸のように、お互いに独立し た性格です。x 軸と y 軸は、それぞれ関係がありません。同じ x 軸の値に対して、 y 軸の値は自由に選ぶことができ、適切に両者を選択することにより、すべての空 間を指示することができます。3次元空間では、x-y-z の三つの直交した座標を必 要とし、多次元空間では、より多くの基本的な座標、ないしはベクタを必要としま す。特殊相対論の四次元はよいとして、それ以上になると、物理的なイメージを得 るのが難しくなりますが、独立した多くの要素から構成される、多次元空間の世界 は、いたるところにあり、多くの場合、その基本的な座標系、すなわち、直交性を 見いだすことが、本質的な問題になります。

さて、必要最小限で完ぺきという、直交性の感覚を得たところで、今度は、「分割」 について考えましよう。植民地政策の「分割統治」は感心しませんが、まずいのは その目的で、手法そのものは、すこぶる効果的です。分割すれば、仕事は驚くほど 簡単になります。コンパイラのように、どうすればよいのかわからないような、複 雑なプログラムも、

  字句解析 -> 構文解析 -> 最適化 -> コード生成
と、仕事を分割することにより、実現可能になります。サラリーマンの場合は、自 動分割されているため、政治的影響力がないのかもしれません。

ところで、Unix の場合は、ある問題を解く際に、分割しても速度が落ちないこと が保証されていて、これがパイプラインの威力でした。そこで、現実の多様な仕事 の処理を、汎用性があって、相補的、効率的かつ基本的な操作に分割することを考 えます。つまり、自分の工具箱とか、万能ナイフに加えるべき道具を考えるといっ た問題ですが、数学的感覚だと、仕事に必要なツールの空間で、直交性を有するコ マンド、すなわち座標を見いだす問題になります。言い替えれば、次のとおりです。

  --------------------------------------------------------------
  すべてのコマンドは、分かりやすく、単純で基本的なもので、それぞ
  れ別な働きをします。機能に関して、重複がありません。そして、そ
  れら少数のコマンドの組合せにより、すべての仕事空間、つまり現実
  の問題をカバーできるようにします。
  --------------------------------------------------------------
これが、Unix のコマンドの目標です。深い思索と長い経験、試行錯誤に裏打ちされ た、Unix のコマンドは、この直交性をほぼ実現しています。つまり、ユーザがその 仕事を分割するための、優れた枠組みを示す、実に洗練されたツールであり、シス テムデザインの指針なのです。

例えば、

  grep $1 *
あるいは、
  for i in *
  do
  	grep $1 $i /dev/null
  done
は、カレントディレクトリのすべてのファイルから、アーギュメントで指定された パターンを探すための、sh スクリプトになりますが、最新のファイルから順に探 すときは、単に、
  for i in `ls -t`
  do
  	grep $1 $i /dev/null
  done
とするだけですみます。コマンドの直交性がよく分かると思います。/dev/null を リストに入れたのは、grep にファイル名を出力させる、ちょっと汚いトリックで す。`ls -t` は、sh の巧妙な引用機構のひとつで、"ls -t" コマンドの出力文字 列を「引用 quot」します。

今度は、ls の出力をマルチカラムにすることを考えてみます。BSD の ls はなに もしなくてもこれができるわけですが、これが、よかれあしかれ BSD 全体に見ら れる傾向で、コマンドの独自性と、モノリシックな性格が強まっています。

Sys_V のユーザだと ls -x とするのが普通でしょう。それ以前なら、 ls | paste - - - - - でもマルチカラムにはなりますが、名前の長さが揃っていないと、凸凹します。こ の問題について、Kernighan さんたちは、

  R.Pike and B.W.Kernighan,- Program Design in the UNIX Environment
	(AT&T Bell Laboratories Technical Journal Vol.63,No8,October 1984)
という、Unix のコマンド設計に関する意見で、マルチカラム出力は、独立したコマ ンドにすべきでないかと述べるとともに、ls の出力を5カラム表示するための最初 の試作品として、次のような例を持ちだしています。
  ls|5
この "5" コマンドの中身は、次のような1行の sh スクリプトです。
  pr -$0 -t -l1 $*
$0 はプログラムの名前ですから、このスクリプトは、ファイル(コマンド)名 "5" だけでなく、
  ln 5 2; ln 5 3; ln 5 4
等により、2, 3, 4, .. といった名前にリンクしておくつもりなのです。実際にテ ストしてみると、スムースに動くのが実感できると思います。こういった使い方が、 Unix の典型的なアプローチです。この和訳が、「UNIX原典」(パーソナルメ ディア ISBN: 4-89362-014-2)にも収録されていますが、大きな脱落があります。

MS-DOS のようなシングルタスクのOSでは、どうしても、自分ですべてをやって しまう、モノリシックなプログラムができます。さもないと、仕事が遅くて困るで しょう。その結果、同じような機能を含む、アプリケーションプログラムの数は、 どんどん増えてゆきます。Unix の場合は、逆です。ひとつひとつのコマンドには、 ごく基本的で、汎用の機能しかありません。それを、パイプラインで結合して使い ます。ですから、Unix のコマンドは、仕事の分割単位であり、複雑な仕事を組み立 てるための部品です。部品の数はかなり少ないのですが、これでほとんどの仕事が できてしまいます。また、当時のテキスト領域64K、データ領域64Kの機械で、 あれだけの仕事ができた理由でもあります。この直交性を忘れなければ、これだけ のアドレス空間で、驚くほどの仕事ができます。

Unix の場合、何かしたいと思ったら、まず既存のコマンドで解決しようとするこ とが大切です。プログラムを作るのは、よくよくのことです。Unix の思考法で考 えると、簡単できれいに解決できることが多いものです。その過程で、Unix のコ マンドの直交性が見えてきて、感動できるようになります。

以下、コマンドの直交性を、もう少し別な角度からながめてみましょう。詳細につ いては、マニュアルを見ながら、実験し、自分自身の問題について、考えてみるの が一番です。

部材と結合材

このような事情で、cat, cd, cp, date, echo, ed, file, grep, lp, ls, mail, mkdir, mv, pg, pwd, rm, rmdir, sort, spell, stty, tail, tty, vi, wc 等、 Unix のほとんどのコマンドは仕事を分割、あるいは構成するための部材です。そ れだけでは完成した仕事になりません。これらの部材をまとめて、仕上げるための 結合剤が、sh と awk になります。

これらの網羅的解説は、最後にご紹介する文献や、Unix のマニュアルそのものに ありますので、ここでは、前記の解説の具体化と、マニュアルを見るための手がか りやトピックスを拾ってみたいと思います。

sh

sh は、コマンドの実行、リダイレクト、パイプラインによる結合、プロセスの制 御といった Unix のメカニズムをユーザに解放する仕事だけでなく、コマンドを仕 事にまとめるための巧妙なメカニズムをもっています。例えば、パイプラインとと もに、Unix の優れたメカニズムのひとつとして、MS-DOS にも取り入れられて、す っかり日常化した、入出力のリダイレクトひとつとっても、

  <file		標準入力を file にする。
  >file		標準出力を file にする。
  >>file	標準出力を file に追加。
  <<[-]word	標準入力を次の行から word の行の直前までにする。
  2>&1		エラー出力を標準出力にマージする。
  <&-		標準入力をクローズ
  >&-		標準出力をクローズ
  >log 2>&1	標準出力とエラー出力をマージして log に記録
  ..
といった多彩なものがあり、例えば、ヒアドキュメント here document と呼ばれ る、<< のメカニズムには、目がさめる想いです。

コマンドとそれが使用するファイルがセットになるというのは、よくある状況です。 なにも考えなければ二つのファイルができてしまいますが、ヒアドキュメントのメ カニズムがあると、それをひとつにまとめることができ、ディスクの効率、読みや すさ、保守のしやすさが、とてもよくなります。例えば、

  grep $1 <<+
  ウチヤマ カオル:内山薫:03-300-5432
  ヤマダ タロウ:山田太郎:0263-12-4326
  ..
  +
といった具合いに、電話番号簿のファイルと、それを検索するコマンドがひとつに なります。

同じアイデアで恐縮ですが、memo コマンド、つまり、

  cat <<END-OF-FILE
  自分のメモ
  ..
  END-OF-FILE
も便利です。もちろん、これらの sh スクリプトには、実行パーミションが必要で す。ヒアドキュメントの EOF (End Of File) マークは、1行単位で比較されます ので、途中のデータ行と一致しないような文字パターンなら何でもよいのですが、 ! とか + の1文字が使われることが多いようです。後で、この二つの sh スクリ プトをひとつにまとめる工夫をしてみてください。つまり、grep の検索パターン を指定しない場合は、すべてのデータを表示するようにします。

sh にはこの他、通常のプログラミング言語にみられる、制御文、マクロ(置換)、 引用、記憶、デバッグのためのメカニズムといった多くの機能がありますが、これ らはすべて、他のコマンドとの協調を前提にしています。例えば、これもコマンド の直交化のよい例になると思いますが、利用者が数値のつもりでいる sh 変数 a の内容をデクリメントする演算は、

  a=`expr $a - 1`
のように、expr コマンドの出力を引用する方法で行なうのが普通です。

a は sh 変数の名前、$a は a の内容です。sh には、a、time といった、普通の プログラミング言語によく見られる、アルファベットで始まる名前、つまり、「キ ーワードパラメータ」と呼ばれる変数の他、awk と同じように、$1, $2, .. とい う、レコードのフィールドを表わす変数があって、こちらは、「位置パラメータ」 と呼ばれます。フィールド変数といった理解のほうがよいかもしれません。

位置パラメータは、sh を起動したときのアーギュメントの受渡しに使われる他、 具体的には後で触れますが、set という sh の内蔵コマンドが、文字列レコード のフィールド分解を行なうために使います。

キーワードパラメータの方は、sh が自分自身で使う以外に、export して、他のコ マンドにデータを渡す、つまり「環境」を作るためにも使われるのが sh らしいと ころです。

また、変数の内容との置き換えを指示する、sh の $ 、つまり「マクロ」には、単 にパラメータの内容と置換えるだけでなく、未定義等の条件に対応するいろいろな 機能があります。

このように、sh そのものは数値を扱えません。すべてが文字列として処理されま す。この点を誤解すると、わけがわからなくなりますので、注意してください。数 値は、expr コマンドで処理され、sh はその結果を文字列として引用します。そし て、その sh 変数が扱える唯一の対象である、文字列の扱いも、他の言語と比べる と、一風変わっていて、例えば、時刻の「時」「分」がほしいとします。date コ マンドの出力文字列を分解すればよさそうですが、よく行なわれるのは次の方法です。

  X=$IFS				# 以前の IFS を保存
  set `date`; IFS=:; set $4	# $1 に「時」、$2 に「分」を得る
  IFS=$X				# IFS を復旧
この動作は、次のとおりです。まず、date コマンドは、次のような文字列を出力し ます。
  Sun May 20 15:07:13 JST 1990
sh のフィールドセパレータ IFS の初期値は、空白文字ですから、set コマンドは、 この文字列、すなわち「レコード」を、次のように分割します。
  $1	Sun
  $2	May
  $3	20
  $4	15:07:13
  $5	JST
  $6	1990
set は、パラメータがあると、それを「レコード」と見なして、フィールドセパレ ータ IFS で分解し、$1, $2, .. にセットします。$# つまり、フィールド数は 6 になります。

次に、フィールドセパレータ IFS をコロン (:) に切り替えて、$4 すなわち、 "15:07:13" のレコードをフィールド分解すれば、

  $1	15
  $2	07
  $3	13
と、時、分、秒が得られます。

$1, $2, .. が、awk のフィールドと同じ表現だということに注意してください。 フィールドセパレータ FS を使用した、awk のフィールド分解と同じメカニズム で、システム全体からみると、一貫性があります。sh にせよ、awk にせよ、代入 という概念より、ファイルのレコード構造を意識した、フィールド分解という概念 が先行します。例えば、awk で税額計算等を行なう場合の、作表に相当する、配列 の初期化は、

  split("50 100 150 200 250 300", h_koujo)
といった方法で行ないます。"50 100 .. " は、レコードなのです。sh の set と まったく同じ感覚です。

sh の文字列操作のよい例として、ルート (/) デバイスの余裕を調べることを考え てみましょう。そのためには、まず、ルートデバイスの空き領域を df コマンドで 求めなければなりませんが、例えば、

  rspace=`expr "\`df 2>/dev/null\`" : '.*: *\([0-9]*\)'`
等とします。expr コマンドの「一致演算子」(:) は、: の左側の文字列を右側の正 規表現と比較して、一致した文字数を返す他、ed と同じ、
  \(正規表現\)
という記憶メカニズムによって、\( \) で囲まれた正規表現にマッチした部分を返 すことができ、この場合は、df の出力の数値部分を抜き出すために使われています。 この例は、sh で数値や文字列演算を行なうための、代表的なものです。sh には、 MS-Basic の left$(), mid$(), right$() とか、HP やご本家の Basic に見られる、 サブストリングのような、文字列演算はありませんが、expr コマンドが、正規表現 を含む、Unix 共通の文字列処理を引き受けています。

このように、sh そのものは、「他のコマンドを動かし、その結果を文字列として引 用する」のがその役割で、多様な引用 quot メカニズムは、そのために存在するの でした。そのため、

  \c	c を文字として引用。* ? [ ! | < > 等のメタキャラクタを文字と
  		して使うために使用。
  ' .. '	引用部は変化しない。空白を含むすべての文字をそのまま引用
		する。
  ` .. `	コマンド置換。指定されたコマンドの出力を引用する。$ 置換
		は実行される。
  " .. "	パラメータ置換 ($) と、コマンド置換 (` .. `) を行なった結
		果を引用する。
といった引用法の理解が必須です。例えば、
  n=1
  until test $# = 0
  do	echo "$n: $"1
  	shift
  	n=`expr $n + 1`
  done
といった sh スクリプトを try という名前にして、
  try date 90 05 27

  try 'date 90 05 27'

  try `date`

  try "`date`"

  try '`date` 90 05 27'

  try `date` 09 05 27
等をテストしてみると、この「引用」の感じがよくわかると思います。複雑な引用 で、動作がよくわからないときは、
  sh -x
と、-x フラグをセットしてみてください。こうすると、実際にどういうコマンドが 実行されているのかが、解ります。このフラグの解除は、
  sh +x
です。

この、コマンドの実行とその結果の引用こそ、人間がコンピュータに向かって、い つもしていることなのだ、ということに注意してください。つまり、人手でコンピ ュータを対話的に動かしているのでしたら、sh は不用です。最終的には、エディ タだけで済むかもしれません。コマンドを起動して、その結果を引用し、その結果 を見て次のアクションを考えるという、人間のすることを自動化しようとすると、 sh が必要になります。

sh は、Unix の中でもわかりにくいコマンドのひとつではないかと思いますが、そ の理由のほとんどが、データを扱うのではなくて、コマンドやファイルシステムを 扱うためのプログラムだということにあります。いろいろなコマンドの機能や使い かたと一緒に理解しないと、その意味がわかりません。Unix のプログラマにとって システムコールが Unix だとしたら、Unix のユーザにとって sh は Unix そのもの です。取りつきにくい sh が重要な理由も、まさに、この点にあります。

sh の理解こそ、Unix 理解の鍵です。csh のヒストリ等、対話のメカニズムに目を うばわれる前に、sh の思想や意味を理解しようとしてみてください。そうすれば、 sh だけでたくさんの仕事をスムースにこなしている人々、sh で充分と考えている 人々を理解できるかもしれません。sh のプログラミングは、とても面白いものの ひとつです。

awk

sh に比べると awk の方は楽です。awk は Unix の奇跡のひとつで、pascal が教育 的だとしたら、c は実用的、awk は実務的といった感じになります。よくもこうい う変な言語ができたものだと思いますが、理論の Aho、教育の Kernighan、実務の Weinberg さんといった印象の、異質な天才の協力作業の結果なのかもしれません。 隙だらけのくせにやられない、めちゃくちゃみたいで、役に立つといったところが あります。

awk の特徴は、

  BEGIN { アクション }		# 初期化
  条件 { アクション }		# EOF までの繰り返し
  END { アクション }		# 後始末
という、ファイル処理の基本が、予め組み込まれているので、無意識に、素直な良 いシステム設計ができること。データ処理の基本となる、チェック/アクションを 基本とした構文と、正規表現の組合せにより、恐ろしいほど短いプログラムで済む ことにあります。パイプラインがフィルタの概念を生んだように、この awk の性 格が、"One Liner" という、1行プログラムの概念を生みだしました。というより、 それがもともとの意図だったようです。

また、assosiative array という snobol 言語に影響された配列も、優れた特徴で す。これは、通常のプログラミング言語にみられる、連続したメモリを確保して、 連続した整数のインデックスによりアクセスするという、効率化を宗とした配列で なく、任意の文字列をインデックスとしたデータのセットです。連想配列という訳 語の「配列」という言葉にまどわされると、わからなくなります。技術的には、ハ ッシング、リスト、ツリーといった方法で実装します。配列ではありませんから、

  a[2] = "second" a[78] = 78
を実行しても、2組分のデータスペースがヒープエリアと呼ばれる、そのプロセス の一時的なデータ記憶域にに取られるだけです。この機構を利用すると、例えば、 社員の毎月の給与データが、
  社員コード 総支払い額 社会保険料 ..
というフォーマットで、kyuuyo.01, kyuuyo.02, .. というファイルに収容されて いるとき、
  awk '
  {
    shiharai[$1] += $2
    hokenryou[$1] += $3
  }
  END {
    for i in shiharai
  	print i, shiharai[i], hokenryou[i]
  }' kyuuyo.[01][0-9] | sort
と、とても素直に集計することができます。これが、スピードの犠牲のうえに得ら れるものです。もちろん、これはまた、64K+64Kという、当時のちいさなミ ニコンピュータのメモリ空間に対する回答でもあります。

この配列の実現法以外にも、awk が遅い原因がたくさんあります。awk は、そのソ ースコードを構文木に変換してメモリに置き、その後インタプリーティブに実行す るインタプリータですし、速度より汎用性を意図したルーチンを使います。awk の 特徴は、何より、その使いやすさです。awk が遅いと感じられる場合は、使い方そ のものが間違っている可能性が大です。また、awk だけで仕事をしようとすると、 歪がでます。Unix の場合は、常に、その全体に、絶妙なバランスがあります。awk コンパイラもありますが、間違った awk の使い方の解決にはならないと思います。

awk はまた、他のコマンドの隙間を埋める、あるいは、インタフェースを確保する 仕事にも向いています。データやフォーマットの合わないコマンドの間で、その変 換を受け持ったり、人間とのインタフェースを目的にして、printf による、フォー マッティングだけを行なったりするといった使い方です。例えば、行番号付きのリ ストがほしいというときは、

  awk '{ print NR "\t" $0}' file
とか、
  awk '{ printf "%4d %s\n", NR, $0}' file
などと使います。
	awk '{ printf("%4d %s\n", NR, $0) }' file
と、c のスタイルで書いてもかまいません。両方の構文を受け付けるようになって います。そういえば、以前、
  Christpher J. Van Wyk,- AWK as Glue for Programs
  	(Software-Practice and Expeience, Vol. 16(4), 369-388)
といったレポートを見かけたことがありましたが、これは、awk の話というより、 IDEAL という、pic と同じ趣向の troff のプリプロセサの話といった印象でした。 IDEAL は、数学の group, ring とのごろあわせでつけた名前のようですが、座標 やその計算を複素数表現で扱うとか、いくつかのアイデアを組み合わせた、変わっ た言語です。この表題の、糊 glue というのは、awk の性格の一面をよく表わして いると思います。awk の認識は、人それぞれで、Unix のレポートライタだと言う 人もいますし、汎用言語と見る人もいます。私たちには、Unix コマンドの残り、 逃げ道、万能薬といった印象です。

awk は、理解と記憶の負担が少なく、応用も容易で、初めてプログラミングを教え るのに非常に適しています。basic や pascal、c で落ちこぼれた人々が、awk で 元気にプログラミングしている姿をよく見かけます。アセンブラや、より高級な言 語の機能単位に比べて、コマンドや関数、演算子、制御機構の選択が、より実務的 で、考えたり悩んだりすることが少なく、素直なコーディングができるというとこ ろが優れているのではないでしょうか。

awk には、現在二つのバージョンがあります。新しい方は nawk (new awk)と呼ばれ ることが多いようですが、以前のバージョンに、ユーザ定義関数、任意のファイル からの入力機構、c に合わせた新しい構文、sh を介さないコマンド実行結果の読 み取り、といった新しい機能が追加され、Unix 的でない、独立した言語としての 性格を強めています。しかし、BSD を初めとして、古い awk しか動かないシステム も多く、Unix 本来のコマンドの直交性や、フィルタとしての自然なシステム設計と は、矛盾もでてきます。Unix で使うのでしたら、まず、昔の awk の範囲で使って みるほうがよい習慣がつくと思います。

終わりに

以上、私たちにとって Unix の基本的な足場、思想、あるいは本質に見えた「テキ ストファイル」、「正規表現」、「コマンドの直交性」の三つを軸にして、Unix を理解し、応用するための視点を確保する努力をしてみました。これらは、V7 か ら Sys_III Unix の、非常にはっきりした特徴です。BSD の研究版や Sun OS、 Sys_V 等の商品化されたバージョンでは、プログラムの作者の自己主張が増えて、 この性格が薄まるとともに、よけいなものも増えてきました。それが、先ほどご紹 介した「OSのバイブル」と呼ばれる

  Andrew S. Tanenbaum,- OPERATING SYSTEMS DESIGN AND IMPLEMENTAION
731 ページ (C MINIX USERS' GUIDE) では、次のように書いてあります。
	.. minix was designed with the idea of being similar to Version 7 
UNIX, the last version of UNIX produced by Ken Thompson, Dennis Ritchie, 
and the other members of the Computing Science Research Center at Bell Labs.
Subsequent versions from AT&T, Berkeley, and other sources have tended to 
acquire many features, just ships acquire many barnacles when they have been
in the water too long ..
「minix は、ケン・トンプソンとデニス・リッチー、そしてベル研のコンピュータ 科学研究センタのメンバの手になる最後のバージョンである、V7 Unix と同じ思想 により設計された。AT&T、バークレイ、その他による、この後のバージョンでは多 くの拡張が行なわれ、、長い航海により、船底に牡蛎殻がいっぱいついた船みたい になってしまった。」という意味です。

そして、そのトンプソンさんが 1989 年 7 月に来日されたときの、まことに興味 深い対談が、「bit」の 1990 年 1 月号に掲載されました。「我々はいらなく なった機能をどんどん捨てたのです」という Unix 本来の姿がよくわかります。 そして、「ワークステーションは非常に高い買物」、「短いコードを書いて、それ が動くことを確認してから、さらに長いコードを書くというやりかただから、特別 なデバッガは使わない」、「私は従来の Unix のアプローチが好きです。つまり小 さなプログラムが複数個組合わさって動くようなものがいいですね」等、一言一言 がとても印象に残りました。また、1984 年 12 月号の「bit」には、このお二人 の感動的な「ACMチューリング賞」授賞講演があります。

Unix は、何を作るにしても楽なシステムですから、ほとんど、何にでも仕立てるこ とができますが、その本来のシステムが持つ、小さく、美しく、簡素で無駄のない、 柔軟な姿で使い、大きな成果をあげ、気持ちよく暮らしているている人々も多いの です。そういった人々は、Unix の上でアプリケーションを動かしているのではなく て、Unix そのものを素直に使っています。そして、その使い方は、ほとんどの場合、 その人にとって最良で感激に値するものであっても、他の人々には、あまり役にたた ないものです。Unix は、そのすばらしいサービスの代償として、多分、すこし、自 分で考えることを要求します。

この種の解説が Unix に対する過剰な期待や過小評価にたいする解決になるとも思 えませんが、フィギュアスケートのスピンが、はた目に見えるように、回っている のではなくて、ターンしてバックで小さな円周軌道をすべっているのだということ。 ターンの方が本質的なのだということを理解する程度の役にはたつと思います。少 なくとも、大人が覚えるときには、役にたちます。考えてみれば、それしかないは ずですが、見た目と実体が一致しないとか、ちょっと目には、肝心な部分が見えな いということは多いものです。そして、そこにまた、発見の喜びがあるのでしょう。

最後に、このあたりの Unix にぴったりの参考書をいくつか、あげておきます。ま ず、

  S.R.Bourne,- UNIXシステム
	(マイクロソフトウェア)
は、Unix の全体を理解するのでしたら、多分最良の本です。原著 (ISBN: 0-201- 18484-2) は、Sys_V にあわせて改訂されました。コマンドリファレンスやプログラ ミングの解説書としても役に立ちます。

また、

  Brian W.Kernighan, Rob Pike,- UNIXプログラミング環境
	(アスキー出版)
も、すばらしい本です。

V7 時代の完全なマニュアルは、次の文献にまとめられています。当時の雰囲気が 実によくわかりますし、最近のマニュアルより明快で、失われた新鮮さがあります。 幼児語の氾濫する初心者向けの解説は冗談としても、こうしたレベルの本がもうす こし、ほしいと思います。最初の1巻は Unix のオンラインマニュアル、2巻目は、 Unix のドキュメントで、Unix の概要、初心者向けの解説、tbl, roff, ms といっ た文書作成システム、yacc, lex, f77, ratfor, m4, sed, awk, dc, bc, as とい った言語とそのサポートシステム、Unix のセットアップ、カーネルの作成、Unix のI/O システム、c、uucp、セキュリティの解説といった、ほとんどの分野をカバ ーしています。最近も、新刊書の案内で再販を見かけた覚えがあり、すごいロング セラーだと思いました。

  Bell Lab.,- UNIX programmer's manual vol 1, 2
	(Holt,Rinehart and Winston 1983)
	ISBN 0-03-061742-1, 0-03-061743-X
sh プログラミングの解説は書くのも難しそうですが、次の本が気に入りました。
  Rod Manis, Marc H. Meyer,- The UNIX Programming Language
	(Horword W. Sams & Co.) ISBN: 0-672-22497-6
awk については、既に作者の解説、
  A.V.エイホ, B.W.カーニハン, P.J.ワインバーガ,- 
	プログラミング言語AWK
	(Addison-Wesley Toppan) ISBN: 4-8101-8008-5
が出版されていますが、前記 V7 のマニュアルを含む、初期の解説には何とも言えな いよいところがあり、機会がありましたら、ぜひ、ご欄になってみてください。

平林浩一・小枝子, 1991

この原稿は、雑誌「プロセッサ」(1990/08) の特集記事「MINIXで学ぶUNIX」の記事 をわずかに修正して、HTML に書き換えたものです。当時のハードウェアと比べると、 今のハードウェアは信じがたいほど強力になって、大きなプログラムの負担も減り ましたが、Unix の思想の重要性は今でもまったく変わりませんし、こういった基礎 的な資料も少なくなりましたので、お役にたつこともあろうかと思います。