Bourne Shell 自習テキスト

(C) 1994-06-29 木村 孝道

シェルとは

シェル (shell) とは unix のコマンドインタプリタで、ユーザ端末から入力され た文字列を解釈し、その指示に従って仕事をするプログラムです。しかし、シェル は決して特殊プログラムではありません。シェルも他のツールと同様にunix上の1 つのコマンドに過ぎません。シェルが他の多くのプログラムと違う点は自分自身が ある特定の仕事をするのではなく「他のコマンド類のまとめ役」として機能するこ とです。

なぜコマンドインタプリタが「殻」を意味するシェル (shell) なのでしょうか。 ユーザから見るとオペレーティングシステムの核(kernel)を貝殻のように包んでい ることに由来しているようです。Rod Maris, Marc H. Meyer著「The UNIX Shell Programing Language」には名前の由来について次のような記述があります。

It is called the shell, beacuse, like the shell of a nut or an egg, it is the part that we see from the outside. The inside part is called the kernel. The shell takes to us and to the kernel.「それは、胡桃や卵の殻のよ うに、外から眺める部分なので、シェル(殻:shell)と呼ばれている。内部は核 (カーネル:kernel)と呼ばれる。シェルはカーネルと我々との仲立ちをする。」
kernelとはオペレーティングシステムの中枢で、プロセスやメモリ、ファイル等の 管理を行う部分です。unix上で何か仕事をする時には必ずお世話にならなければな らないものですが、ユーザ側からkernelに働きかける手段はシステムコールのみで す。プログラムを書くのでしたらシステムコールを操れますが、対話形式ではシェ ルを通す以外触れる方法がありません。シェルを通して備え付けのコマンドなり、 自分の作ったプログラムを起動して初めてユーザはkernelへなんらかの指示を出す ことができるわけです。プログラムを書かないユーザにとってはシェルこそがオペ レーティングシステム、あるいはコンピュータそのものに見えますから、シェルを 使いこなすことがそのままコンピュータを使いこなすことになるわけです。

unix の世界標準「POSIX」のドラフトでは唯一のシェルとされる「Bシェル (Bourne Shell)」を体験しながら unix の心髄に触れてみましょう。

シェルの機能

シェルのもっとも簡単に実感できる機能はユーザとの対話形式で入力された文字列 (コマンド)を解析し、指示された仕事を行うものでしょう。「対話形式」だけを 考えるとユーザから入力された文字列を修正する機能や、入力を記憶しておき必要 に応じて呼び出す履歴機能などに注目されます。しかし、対話形式での操作はシェ ルの提供する機能の一部分に過ぎません。シェル本来の機能は「他のコマンド類の まとめ役」にあります。

この機能を上手に組み合せるとわずかな手間でかなり複雑な仕事をこなすことがで きます。ひとつの仕事を能率良く処理できるよう、独立した単純なコマンド類をま とめるため、シェルは次のような機能を持っています。

1. プログラムの実行
2. ファイル名の置換
3. 入出力の切り換え
4. パイプ機能
5. ユーザの環境設定
6. インタプリタ型のプログラミング言語

プログラム(コマンド)の実行

シェルはユーザが端末から入力したプログラム(コマンド)を実行する機能を持っ ています。コマンドの入力が完了するとそれを解析し、どのプログラムをどのよう に実行するかを決定します。この、シェルに対して入力された文字列はコマンドラ インと呼ばれ、一般的に次の様な形をしています。

$ コマンド名 [引数1 引数2 .. 引数n]
行頭の「$」はシェルがユーザからのコマンドを受け付ける状態にあることを示すプ ロンプト(入力を促す文字列)で、ユーザがタイプするものではありません。引数を 必要としないコマンドもたくさんありますから省略することもできます。コマンド 名や各引数間は一般にホワイトスペース文字と呼ばれる「スペース(0x20)」ある いは「水平タブ(0x09)」で区切ります。

コマンドラインでホワイトスペース文字が重複した場合は区切りとして認識される だけで、重複した個数については無視されます。まず、次の例を見てください。

$ echo I drink coffee
I drink coffee
$
この時、シェルはコマンドラインを走査し、実行すべきコマンド名として行頭から 最初の区切り文字までの文字列「echo」を取り出します。続いて次の区切り文字ま での文字列「I」を「echo」への最初の引数として取り出します。同様な操作をコ マンドラインの最後まで繰り返し「drink」と「coffee」を第2、第3引数として 取り出します。コマンドラインの解析が終了するとシェルはここで取り出した最初 の文字列「echo」をコマンドとし、残る文字列を引数としてコマンドを起動します。 echo コマンドが終了すると再度プロンプトを表示してユーザから次の入力を待ちま す。こんどは次の例を見てください。
$ echo Do you        enjoy              unix?
Do you enjoy unix?
$
これは先に説明したように区切り文字が重複していても区切りとして認識するだけ で重複個数は無視されるために起こります。echoコマンドはシェルから受け取った 引数を1個のスペースで分けてユーザの端末に表示するだけなのです。

この2つの例からコマンドラインを解釈するのはechoなどのコマンドではなく、そ れを起動するシェル自身だということを理解してください。シェルにより起動され た echo はシェルから渡された引数を見て仕事をしているだけで、実際にコマンド ラインとして入力された文字列がどのようなものかは知らないのです。

コマンドの入出力モデルとシェル

シェル自身もそうですが、起動されたコマンドは unix のファイルシステム上で動 作しています。ここには標準入力(stdin)という1つの入力モデルと、標準出力 (stdout)、標準エラー出力(stderr)と名付けられた2つの出力モデルが用意さ れています。これら3つのモデルを用意したことが unix の成功の一因になってい るわけですが、起動するコマンドのためにこれらを用意し、調整するのがシェルの 大きな役割なのです。

後から詳しくふれるリダイレクトやパイプ処理は、このシェルによる入出力モデル の調整機能によるものです。このために、コマンドは標準入力から読み込み、標準 出力あるいは標準エラー出力に書き出す極めてシンプルな構造にもかかわらず、他 のプログラムと親和性の良いものにすることができるわけです。

次の図はシェルにより起動されたコマンドの入出力を概念的に描いたものです。 プログラムが直接ファイルを読み書きするのはシェルとは直接的関係がないので ここではオプションとしてみました。標準入力 (stdin) と標準出力 (stdout) は パイプラインに接続可能、標準エラー出力は普通は端末に出力されますが、リダイ レクトすれば、パイプラインに接続可能です。

     引き数  標準エラー出力
       ↓  ↑
      +−−−−+
標準入力 →|コマンド|→ 標準出力
      +−−−−+
        ↑↓
      (ファイル)

ファイル名の置換(展開)

シェルはコマンドラインを解析しているときに特殊文字「*」「?」「[ ]」を見つけ るとその特殊文字部分をカレントディレクトリにあるファイル名での置き換えを試 みます。次の例をみてください。
$ ls -C
Flopsy     Mopsy     Cotton-tail     Peter
$ echo *
Flopsy Mopsy Cotton-tail Peter
$
シェルは与えられたコマンドラインの解析を始めます。そして特殊文字「*」を見つ けると、カレントディレクトリ内にあるすべてのファイル名で「*」部分を置き換え ます。そのあとにシェルは起動すべきコマンド名と渡すべき引数を決定します。

echoは特殊文字「*」を解釈することも、存在していることさえも分かりません。 このとき、echo は4個の引数がシェルから与えられた事を認識しているだけです。 このようにシェルは grep や ed などに比べると制限されているものの、次のよう な正規表現を解釈することができ、これらの文字をメタ文字と呼びます。なお、シ ェルによるメタ文字の展開はファイル名のみが対象となります。

特殊文字 意味
--------------------------------------------
  *     0個以上の任意文字と一致
  ?     任意の1文字と一致
 [ ]    [ と ] で囲まれた文字のいずれかと一致
さて、ここで前項で例とした
$ echo Do you        enjoy              unix?
Do you enjoy unix?
$
を考えてみてください。このコマンドラインにはメタ文字「?」があることに気が ついたでしょうか。ところがechoの出力を見るとシェルにより置換が行なわれず にそのまま表示されています。これはシェルが手抜きをしたわけではありません。 シェルはコマンドラインの解析で「?」を見つけています。そしてカレントディレク トリにあるファイル名との置換を試みています。しかし、置換条件を満たすファイ ルが見つからないので置換に失敗し、入力された文字列をそのままを引数として echo に渡してしまうのです。このために「unix?」がそのまま表示されたわけです。

しかし、場合によってはシェルによる置換が思いもよらない弊害を引き起こすこと があります。このためにシェルによってコマンドラインにあるメタ文字の解釈を禁 止する方法が用意されています。(「引用符」参照)

シェルがコマンドラインを解析する手順をまとめると概ね次のようになります。

1. コマンドラインからメタ文字「*」「?」「[ ]」を探す
2. メタ文字が見つかればカレントディレクトリ(注)のファイル名で置換を試みる
3. コマンドラインから起動すべきコマンドと引数を取り出しコマンドを起動する

(注): 「/usr/bin/d*」とか「../MEMO.*」のように、「/」を含む場合は、指定 されたディレクトリの中のファイル名を使います。

入出力の切り換え(I/O redirection)

シェルは起動したコマンドの入出力先を切り換える機能(I/Oリダイレクト)を 持っています。シェルはコマンドラインを解釈し、リダイレクトを表す特殊文字 「<」「>」「>>」が見つかるとそれに従った処理を行います。次の標準出力をファ イルにリダイレクトする例です。

$ echo It will rain tomorrow >./memo
$ cat ./memo
It will rain tomorrow
$
シェルは出力のコマンドライン上にリダイレクトを表わす文字 (>) を見つけると、 それに続く語で指定されたファイルに(この場合は./memo)出力先を変更します。 この例ではカレントディレクトリにmemoというファイルを作り、そこに echo の 出力を書き出します。結果としてファイル ./memoに「It will rain tomorrow」 が書き込まれることになります。もし、この時にファイル ./memoがなければ新た に作られますし、既にある時には上書きされて古い内容は失われます。

ここで大切なことは、シェルはコマンドラインで指定されたコマンドを実行する前 にその標準出力を「>」に続いて指定されたファイルに切り換えていることと、起 動されたコマンドは単に標準出力に結果を出力しているだけだということです。シ ェルが解釈するI/Oリダイレクトを表す文字とその意味は次のようなものです。

特殊文字      意  味
----------------------------------------------------------------
[n]<file   ディスクリプタ n (省略時 0) で file を読み込み用にオープン
[n]>file   ディスクリプタ n (省略時 1) で file を書き込み用にオープン
[n]>>file  ディスクリプタ n (省略時 1) で file を追加書き込み用オープン
<< eof     標準入力を次行から eof の直前行までとする (here document)
n>&m       ディスクリプタ n の出力をディスクリプタ m に変更
n<&m       ディスクリプタ m の入力をディスクリプタ n に変更
[n]<&-     入力ディスクリプタ n (省略時 0) をクローズ
[n]>&-     出力ディスクリプタ n (省略時 1) をクローズ
----------------------------------------------------------------
[ ] 内は省略可。ディスクリプタはオープンしたファイル番号で 0〜9 の数字で表す

ヒアドキュメント(here document)

特殊文字「<<」はヒアドキュメントと呼ばれる興味深いメカニズムを提供してくれ ます。通常の仕事でコマンドとそこから参照されるデ−タが対になることがしばし ば起こります。例えば電話番号簿などでは検索プログラムとデータファイルの2つ が必要になります。ヒアドキュメント機能を利用してシェルスクリプトを書けばこ れらのファイルを1つにまとめることができ、保守性やディスクの使用効率が良く なります。(「ハノイの塔」参照)

次の例はヒアドキュメント機能を利用した電話番号簿です。

$ cat phone
grep -i $1 << 'END-OF-FILE'
JAL:(03)5489-1111
JAS:(03)3438-1155
ANA:(03)3272-1212
KLM:(03)3216-0771
END-OF-FILE
$ phone ANA
ANA:(03)3272-1212
$
この例を理解するにはシェルスクリプトの基礎知識が必要になりますが、簡単にメ カニズムを説明します。grep は文字列検索プログラムで、$1 はシェルによりコマ ンドラインで指定された第一引数(キーワード)で置き換えられます。シェルは 「<<」を見つけると、それに続く文字列(この例では「END-OF-FILE」を EOF (End Of File) マークとして覚えます。そして、その直後の行から次の EOF マークの直 前の行までをコマンド grep への入力に結合します。ヒアドキュメントの EOF マー クは1行単位で評価されますから途中のデータ行に出現しない文字列を選ばなけれ ばなりません。

エラー出力を標準出力にマージする

コンパイルを行なうときなどは標準出力の記録とエラー履歴を一緒に取りたいもの です。このような時にはコマンドラインで次のような指定をします。

$ make >./make.log 2>&1
$
この動作を追いかけてみましょう。リダイレクト機能表でも分かるように「>&」の 働きは、左側に書かれたファイルディスクリプタの出力を右側に書かれたファイル ディスクリプタに変更することです。シェルは標準入力・標準出力・エラー出力の 3つのファイルをオープンしており、それらはファイルディスクリプタ「0」、 「1」、「2」として管理されています。ですから「>&1」はファイルディスクリ プタ「2」を「1」に、つまりエラー出力を標準出力に変更することになります。 さらに、「>./make.log」として標準出力が「./make.log」にリダイレクトされて いますから、「./make.log」には標準出力とエラー出力がマージされたものが書き 出されることになります。

リダイレクトのメカニズムを理解する上で大切なことは、コマンドラインで記述さ れたものが「右から左の順に評価」されるということです。エラー出力を標準出力 にマージするこの例では、まず最初に「2>&1」が評価されてから「>./make.log」 が評価されます。もし、コマンドラインで

$ command 2>&1 >foo
と書いたならば違った結果になってしまいます。どうなるかはご自分でお試しくだ さい。

パイプ機能

シェルはリダイレクト文字や正規表現をコマンドラインから解釈すると同様に垂直 バー「|」をパイプ記号として識別します。シェルはパイプ記号「|」をコマンドラ インに見つけるとその前にあるコマンドの標準出力を、その後ろにあるコマンドの 標準入力に結合させます。そしてシェルは両方のコマンドを「同時に実行」させま す。次の例を考えてみましょう。

$ who | wc -l
3
$
まず、シェルはコマンドラインを解析して「who」と「wc」の間にあるパイプ記号 「|」を見つけます。次にシェルは最初のコマンド「who」の標準出力をそれに続く コマンド「wc」の標準入力に結合させて2つのコマンドの実行を開始します。コマ ンド who はログインしているユーザのリストを標準出力に書き出します。who の 標準出力は wc の標準入力につながれていますから端末に who の出力は表示され ません。who と同時に起動された wc はファイル名の指定がないために標準入力か らの行数を数えますので、who の処理結果の行数を数えることになります。リダイ レクトの項目でもふれたので繰り返しになりますが、シェルにより起動されたコマ ンドは自分の標準入出力が何に割り当てられているかは知りません。

コマンドの区切り文字

シェルのコマンドラインではセミコロン「;」で区切ることにより複数のコマンド を書くことができます。次の例ではサブディレクトリを変更し、ゴミを掃除してい ます。

$ cd ../misc; rm *.bak *~ *.o core
$

コマンドのグルーピング

シェルは幾つかのコマンドまとめて、あたかも1つのコマンドのように実行するこ とができます。これをコマンドのグルーピングと言います。グルーピングを行なう には一括して実行したいコマンドを小括弧あるいは中括弧で囲んで

( command .. )
{ command ..; }
とします。これら2つはコマンドのグルーピングを行なう点は同じですが、 「( command .. )」はサブシェルで実行され、「{ command ..; }」はカレントシェ ルで実行される点が違っています。この違いを次の例で確認してください。
$ pwd
/usr/myhome
$ (cd /usr/try; ls -C)
Flopsy     Mopsy     Cotton-tail     Peter
$ pwd
/usr/myhome
$ { cd /usr/try; ls -C; }
Flopsy     Mopsy     Cotton-tail     Peter
$ pwd
/usr/try
$
「(cd /usr/try; ls -C)」はサブシェルで実行されるために pwd で表示されるコ マンド終了後のカレントディレクトリは変わりません。一方、 「{ cd /usr/try; ls -C; }」'はカレントシェルで実行されますので終了後のカレン トディレクトリが変わってしまうのが確認できたと思います。

中括弧「{ command .. ;}」を使ってグルーピングを1行に書くときには、「{」の 直後と、「}」の直前には必ず1個のスペースが必要です。さらに中括弧でグルー ピングされたコマンドの末尾にはコマンドの区切りを表すセミコロン「;」がなけ ればいけません。これは中括弧がシェルの予約語であるための制限です。(「構文 規則」参照)

バックグラウンド処理

unixはマルチタスク OS ですから複数のプログラムを同時に走らせることができま す。しかし、特に指定をしない限りシェルは入力されたコマンドの実行終了を待っ てから次のコマンドを受け付けます。プリンタへの出力やテープの巻き戻しなどは 数分、あるいはもっと時間がかかるかもしれません。この終了を待っていては何の ためのマルチタスクなのか分かりません。シェルにコマンドの終了を待たずに次の コマンドの受付ができるようにバックグラウンドで実行するように指示を出すこと ができます。

入力したコマンドをバックグラウンドで実行させるためにはコマンドの末尾にアン パーサンド「&」を付けます。こうするとシェルはプログラムをバックグラウンド で実行し、そのプロセスIDを表示し、すぐにプロンプトを表示してユーザからの次 の入力を受け付ける状態になります。ここで表示されるプロセスIDはバックグラウ ンドで実行されているコマンドを識別する唯一のものです。

バックグラウンドで実行したプログラムは「DEL」キー(OS によっては「Ctrl-C」 キー)によって発生する端末割り込みで中断させることができません。途中で停止 させるにはここで表示されたプロセス ID(ps コマンドで調べることもできます) にキル (kill) シグナルを送ることで行ないます。

$ pr foo | lpr &
215
$

ユーザの環境設定

シェルはユーザの希望する環境を設定できるいくつかの環境変数と呼ばれるものを 持っています。これらはユーザのホームディレクトリやコマンド入力を促すための プロンプト文字列、ユーザが実行させたいコマンドを探すためのディレクトリ・リ ストなどが格納されています。unix で使用される主な環境変数として次のような ものがあります。

HOME

ユーザがシステムにログインした時に、「/etc/passwd」のホームディレクトリ・ フィールドの定義に従って決定されるユーザの家(HOME)とするディレクトリです。 この環境変数「HOME」はユーザのホームディレクトリを識別するためにプログラム から参照することができます。例えば、引数なしで cd コマンドを実行した時にこ の「HOME」が参照され自分の家に迷わず帰ることができます。もちろんこの 「HOME」はユーザが好きなものに書き換えることができます。しかし、不用意に変 更してしまうと「HOME」を参照するコマンドの動作に影響があるばかりか、他のユ ーザに迷惑をかけることになりますから、みだりに変更してはなりません。

プロンプト文字列 (PS1, PS2)

シェルがユーザにコマンドラインの受け付け状態にあることを示すために表示する 文字列は変数「PS1」に格納されます。ログイン後に表示される「$ 」がそうです。 コマンド行が複数にまたがり、コマンド入力状態継続していることを表す二次的な プロンプト文字列は「PS2」が保持しており、通常は「> 」が格納されています。 この変数はユーザが自由に書き換えて構いません。

PATH

シェルはユーザからコマンドラインを解析して起動するプログラムを決定したならば そのコマンドを環境変数「PATH」が示すディレクトリ・リストから探します。この 「PATH」はログイン時に自動的に設定されます。「PATH」を始めとする環境変数は echo コマンドで次のようにすれば、その内容を見ることができます。

$ echo $PATH
/bin:/usr/bin::
$
環境変数「PATH」の前にドル記号「$」を置いてやるとシェルはその部分を環境変 数「PATH」に格納されている内容で置換してから echo に引数として与えます。 この場合は「/bin:/usr/bin::」がコマンド探索用のディレクトリ・リストにな ります。

「PATH」の示すディレクトリ・リストはそれぞれのディレクトリをコロン「:」 で区切って表されます。コマンドの実行が指示されたならばシェルは「PATH」の内 容に従い、「/bin」->「/usr/bin」->「」の順にディレクトリからコマンドを探し ます。このディレクトリ・リストの最初の2つは文字の通りの場所です。3番目の 「」はカレントディレクトリ「./」を意味しています。今まで頻繁に使ってきた echo コマンドは「/usr/bin」にありますから、コマンドが入力されるたびにシェル はこれらのディレクトリ・リストを参照して探していたわけです。

コマンドラインでコマンド名にパスを含めた指定をするとシェルは「PATH」の示す ディレクトリ・リストを無視して直接指定されたコマンドをだけを探します。例え ば、

$ /bin/date
Wed Jul 29 13:42:25 JST 1994
$
とすると、シェルは「PATH」の内容を無視して「/bin/date」を実行します。このよ うな絶対パスだけでなく、カレントディレクトリからの相対パスを指定しても同様 に環境変数「PATH」の内容は無視されます。

TERM

環境変数「TERM」はユーザの端末属性を記憶しています。どのような端末でも 10 進数の「65」というコードを受け取ると「A」という文字を表示します。しかし、 「1」というコードを受け取ると端末によっては強調モードになるかも知れません し、別の端末では反転モードになるかも知れず、動作の保証がありません。スクリ ーンエディタなどの端末を制御するプログラムは適切な制御コードを端末に送らな いと正しい結果が得られません。世間には無数の端末がありますからそれぞれに合 わせたプログラムを一個一個作るのは途方もない時間と労力が必要になり不経済で す。これを避けるために、unix ではたくさんある端末の制御コードをデータベー ス化して「/etc/termcap」というファイルに持っています。この中から自分の使っ ている端末のエントリを環境変数「TERM」に格納しておきます。

termcap については本テキストで触れていません。興味を持たれた方は次章以降で シェルプログラミング教材としている Cabinet から参考文献を探すことができま すので読まれてはいかがでしょう。(「Cabinetの文献リスト」)

umask

ユーザが新しく作成するファイルのパーミッション(許可属性)を決定するための 値を8進数で設定します。ここで指定する値は作成するファイルやディレクトリに 与えたくないパーミッション値と考えてください。

ファイルを作成するときには、そのファイルを作るときのデフォルトパーミッション 値を「umask」で指定された値でマスクしたものが使われます。例えば umask 値を 「022」にしたいならば次のようにします。

$ umask 22
上記設定時には、モード「777」で作成されるファイルのパーミッションは「755」 'となりますし、モード「666」ならば「644」になります。

引数を与えないで実行すると、現在設定されている umask 値を表示します。

.profile によるログイン環境の設定

ここで触れた環境変数を始めとする色々な設定は「.profile」というファイルに記 述してホームディレクトリに置けばログイン時に自動的に設定されます。この 「.profile」はログインと同時に一度だけ実行されるシェルスクリプトです。上記 の環境設定以外にもログインした時に実行させたいものを書いておくことができま す。

インタプリタ型のプログラミング言語

シェルはその中にインタプリタ型のプログラミング言語を持っています。このプロ グラミング言語は多くのプログラミング言語同様に条件判断やループ処理などの機 能を持っており、複数のコマンドのまとめ役としてシェル本来の力を出してきます。 このプログラミング言語の理解を深め、unix の一番おいしい部分を楽しむことが このテキストの最大の目的です。

シェルプログラミングの基礎

今まで見てきたように、シェルには1つのコマンドを実行させたり、また複数のコ マンドを組み合わせて実行する機能ばかりか、あるコマンドの仕事結果を見てさら に次のアクションを起こさせたり、他の処理に分岐させたりする機能も持っていま す。この機能を実現するために使われるのがシェル・プログラミング言語です。

端末に向って仕事をする時間が長いせいかもしれませんが、シェルというと一般的 にヒストリ機能など、対話型の操作性に目を奪われるかも知れません。しかし、対 話性を重視する環境はともすれば人間が機械の忠実な子守役を強いられる可能性が あります。1つのコマンドの結果をみてからそのつど人間が次の仕事を機械に指示 するのは本末転倒で何ともバカバカしいかぎりです。シェルの持つプログラミング 機能はこの人間が行うべき仕事、すなわち1つのコマンドの結果を見て次に起こす アクションをあらかじめシェルに教えておき、実行時にそれらを代行させることに あります。

シェル・プログラミング言語(シェル言語)そのものはいたって単純なものですが、 これを使いこなすには unix 上の各コマンドの使い方を理解しなければなりません。 シェルは各々のコマンドを起動し、その結果を文字列として引用しますが、実際に データを処理するのは unix 上の小さなコマンドたちです。データを処理しようと 考えた時には数あるコマンドの中からどれを、どのようなオプションで、どういう 組み合わせで使えば目的を達することができるかをまず考えなければなりません。

シェルスクリプトと実行方法

人間の仕事をシェルに代行させるための手順を書いたテキストファイルのことをシ ェルスクリプトといます。シエルスクリプトに記述できる内容はコマンドラインで 受け付けられるものならば何でも書くことができます。シェルにはパイプに代表さ れるような複数のコマンドを組み合せる機能や、あるコマンドの実行結果を見てそ れを次のコマンドに引用するためのロジックが組み込まれています。

シェルスクリプトは純粋なテキストファイルですからテキストエディタで書き起こ したり、修正することができます。そして、そのスクリプトを実行させるには端末 からのシェルへの入力を代行させる意味で

$ sh <script
とシェルの標準入力に流し込む方法が使えます。また、シェルは引数があるとそれ を入力ファイルとして扱いますから
$ sh script
としても構いません。しかし、このようにスクリプトをそのつどシェルの引数とす るのは面倒ですから、スクリプトに「実行権」を与えて使います。「実行権」とは unix のファイルシステムに用意されている許可属性の1つで、この権利が与えら れているファイルはコマンドとして直接起動することができます。

テキストファイルとして書かれたスクリプトに実行権を与えるには chmod コマンド を使用して、

$ chmod +x script
とします。このあとはスクリプトを修正してもファイルの実行権が失われることは ありません。

このようにしてコマンドラインからシェルスクリプトを起動すると、カレントシェ ルは子プロセスとしてもう一つシェルを走らせ、そこでシェルスクリプトを実行し ます。この実際にスクリプトを実行するために走るシェルをサブシェルといいます。 子プロセスのサブシェルは親プロセス(カレントシェル)の環境を受け継ぐことは できますが、その逆はできません。例えば、実行属性を与えたシェルスクリプトを 使って環境変数を再設定しようとしても、再設定されるのはサブシェル側の環境変 数であってカレントシェルのものではありません。

現在の環境変数を再設定するにはスクリプトをカレントシェルで実行しなければな りません。このためシェルにはドットコマンドと呼ばれるものが用意されおり、コ マンドラインでドット「.」の後にスペースを置いて実行したいスクリプト名を書 きます。

$ . script
ドット「.」を先頭に置くことによりカレントシェルは「script」を自分自身で 実行しますので、現在の環境変数を再設定することができます。なお、ドット「.」 に続く「script」には実行権の必要はありません。

シェル変数

すべてのプログラミング言語と同様にシェル言語でも変数に値を格納したり参照す ることができます。シェル変数は英文字あるいはアンダースコア「_」で始まり、 その後ろに0個以上の英数字あるいはアンダースコアのならびで表します。

シェル変数に値を格納するにはC言語などと同様にシェル変数と格納したい値を 等号「=」で連結し、

シェル変数=値
とします。この時に等号「=」の両側にスペース文字を入れてはいけません。C や PASCAL などのソースを読みやすくするため演算子の両側にスペースを置く習慣の ある人は要注意です。

シェル変数の初期化と参照

シェル言語では C や PASCAL 等のプログラミング言語と異なりデータ型の概念 がなく、すべての変数は文字列として扱われます。使用する変数は前もって宣言す る必要がなく、シェルは新しい変数を見つけるとそれを登録し、ヌル値で初期化し ます。

シェル変数に格納されている値を参照するにはシェル変数名の前にドル記号「$」 を付けます。シェルはドル記号「$」に続く文字列が正しいシェル変数名ならばそ の変数に格納されている値と置き換えます。何も格納されていないシェル変数をい きなり参照した場合には初期値のヌルで置き換えられます。次はシェル変数 val に「nora」という値(文字列)を格納し、echo コマンドでこれを表示させる例です。

$ val=nora
$ echo $val
nora
$
最後の「nora」は echo コマンドの出力です。このようにシェルは「$val」をその 変数に格納された内容と置き換えてから echo に引数として渡します。いま例に引 いた「val」を使い、「nora_neko」と表示させようとする時には注意が必要です。 先の例の延長で
$ echo $val_neko

$
とするとシェルは「val」ではなく「val_neko」を変数名として処理します。これは、 シェル変数を表す「$」に英数文字が続いているならば最も長い語句を変数名として 切り出すために起こります。ここでシェル変数「val_neko」は未定義ですからヌル 値で置き換えられ何も表示されません。この不都合を回避するには中括弧「{ }」で シェル変数部分を明示してやります。
$ echo ${val}_neko
nora_neko
$
シェル変数は一般の文字列以外にも、メタ文字と呼ばれるシェルにとっては特殊な意 味をもつ文字を格納することもできます。次の例を見てください。
$ val='*'
$ echo $val
Flopsy Mopsy Cotton-tail Peter
$
普通の文字列が格納されている時にはシェル変数``~\$val~''の値を展開するだけで したが、今度はもう少し手のこんだ仕事をしてきます。シェルはコマンドラインに 「echo $val」を受け取るとこれを走査して変数「val」を見つけ、その内容を変数 「val」に格納されている「*」で置き換えます。この「*」はシェルにとっては特別 な意味を持つ文字ですからカレントディレクトリ内の全ファイル名で置き換えたも のを引数として echo を起動します。これらの例からシェルはどのように変数を評 価しているかを理解してください。

ここまでの説明で気が付いたと思いますが、「ユーザの環境設定」でふれた環境変 数はシェル変数そのものです。シェルスクリプト内で使われる一般のシェル変数と は違い、ユーザの使用環境を整えるために使われるのでその使用目的から環境変数 と呼ばれています。

エクスポート変数

あるスクリプトから別のスクリプトにシェル変数値を渡したり、ログイン中に実行 するすべてのコマンドから参照させたいシェル変数は「export」文で宣言するこ とによりその内容を子・孫シェルに引き渡すことができます。「export」文の書式 は以下のようになります。

export val_list
「val_list」は他のスクリプトに引き渡したいシェル変数名のリストで、スペース で区切ってならべます。

ユーザがログイン完了後に走る最初のシェルをログインシェルと言い、すべての親 シェルとなります。ここでユーザが何かスクリプトを実行させるとログインシェル はこのために新しいシェルを走らせます。シェルスクリプトの実行方法を思い出し てください。

$ sh script
これはシェルのコマンドラインで「script」を引数としてもう一つのシェルを走ら せていることをにほかなりません。シェルスクリプトに実行属性を持たせて起動し ても内部ではこれとまったく同じ手順で実行されています。親シェルはこの時に自 分のシェル変数の中で「export」されているものだけをサブシェル側でも参照でき るようにコピーします。このシェル変数はエクスポート (export) 変数とも呼ばれ、 その場所から実行されるサブシェルに対して伝えられていきます。「export」宣言 されなかったものはローカル変数として子孫には伝えられません。

もし、エクスポート変数がサブシェル側で書き換えられたならば、その影響はコピ ーされていく子孫側にだけ及び、親となったシェル側のシェル変数内容を変更する ことは決してありません。親シェル側からすれば出て行く (export) だけです。

readonly 変数

間違って書き換えては困るシェル変数には「readonly」文で読み出し専用属性を与 え、保護することができます。「readonly」文は引数として与えられたシェル変数 を読み出し専用属性にします。一般書式は次のようになります。

readonly val_list
読み出し専用属性を設定されたシェル変数を書き換えようとするとエラーメッセー ジが表示されます。シェル変数をいったん read only としてしまうと再び元の状態 に戻す方法はありません。

なお、現在のシェルでエクスポート変数に read only 属性を与えてもサブシェルに 渡されるのはその変数値だけであり、read only 属性は渡されません。

引用符

シェルはコマンドラインにメタ記号などの特殊文字を見つけると展開や置換を行い ます。これはシェルの持つ便利な機能ですが、時としてユーザの期待とは異なる仕 事をすることがあります。次の例をみてください。

$ echo *  means all files in the directory.
Flopsy Mopsy Cotton-tail Peter means all files in the directory.
$
echo コマンドで「* means all files in the directory.」とメッセージを出力し たいだけなのですが、シェルは自分の仕事を忠実に実行し「*」部分をカレントデ ィレクトリのファイル名で置換してしまいます。さらに「*」と「means」の間にあ る2つのスペースも区切りとしては認識されますが、個数は無視されています。

期待通りの表示をさせるために、コマンドラインをシェルの置換機能から保護しな ければなりません。そのために引用符を用います。シェルにはそれぞれ異なった働 きをする単一引用符「'」、二重引用符「"」、逆引用符「`」の3つが用意されて おり必ず対で使われます。

単一引用符 (')

シェルは最初の単一引用符「'」を見つけるとそれを閉じる単一引用符「'」に出会 うまですべての特殊文字を無視します。先の例では次のようにすれば期待通りのメ ッセージが表示されます。

$ echo '*  means all files in the directory.'
*  means all files in the directory.
$

二重引用符 (")

単一引用符と同様にほとんどの特殊文字を無視します。しかし二重引用符の中であ ってもドル記号「$」、逆引用符「`」、バックスラッシュ「\」の3つについては 認識されます。ドル記号「$」が解釈されますので二重引用符内ではシェル変数の 置換が行われます。また、バックスラッシュ「\」にはその直後に続く1文字の特 殊な意味を取り除く機能があります。

次の例で最初の2つは引用符で囲まれた文字列は同じですが、囲んでいる引用符が 一方は単一引用符で他方は二重引用符です。3つ目のものはシェル変数を示すドル 記号「$」の前に次の文字の意味を無効にするバックスラッシュ「\」が付いていま す。このために「$val」部分がシェル変数ではなく単なる文字列として扱われてい ます。

$ echo '$val means all files in the directory.'
$val means all files in the directory.
$ echo "$val means all files in the directory."
Flopsy Mopsy Cotton-tail Peter means all files in the directory.
$ echo "\$val means all files in the directory."
$val means all files in the directory.
$

逆引用符 (`)

単一引用符、二重引用符はコマンドラインの文字列をシェルから保護する働きがあ りますが、逆引用符「`」はこれで囲まれたコマンドを実行し、その結果を文字列 として引用する機能を持っています。言い換えるならばシェルは逆引用符に囲まれ た文字列をみつけるとそれをコマンドとして実行し、そのコマンドからの標準出力 で逆引用符部分を置き換えます。次の例を見てください。

$ echo "The date & time is: `date`"
The date & time is: Thu Jul 7 13:42:25 JST 1994
$
シェルはコマンドラインを走査しその中に逆引用符で囲まれた「`date`」を見つけ て date コマンドを実行します。そして、その出力でコマンドラインの「`date`」 部分を置き換えます。あとは今まで説明したようにして echo コマンドへの引数を 組み立てて走らせます。

逆引用符内で実行できるコマンドは1つとは限りません。シェルのコマンドライン で許される記述が可能です。次の例のようにパイプを使うこともできますし、セミ コロン「;」で区切って複数のコマンドを書くことができます。また、括弧を使っ てのグルーピングなども可能です。

$ echo "`ls | wc -l` files in your directory."
4 files in your directory.
$

特殊なシェル変数

IFS

この変数にはシェルがコマンドラインを走査するときの区切り文字のリストが格納 されています。通常はホワイトスペース文字と呼ばれるスペース、タブ、改行文字 がこれにあたります。

この「IFS」の内容はユーザの任意の文字に変更することができますから、ホワイト スペース文字以外で区切られた1行から文字列を切り出す時に有効に働きます。こ のため、「IFS」には1つのレコードを構成するそれぞれのフィールドを区切る文字 が格納されていると考えた方がより現実的です。ちなみに「IFS」とは「Internal Field Separator」の略です。

ここでちょっと IFS の内容を見てみましょう。IFS はシェル変数ですから echo に シェル変数を表示させる次のような方法で参照できそうです。

$ echo $IFS

$
しかし、実際試してみると、この例のように続く1行が空くだけで何も表示されま せん。これは IFS の内容が空白文字としてスペース (0x20)、タブ(0x09)、改行 (0x0a) から成っているためです。ちょっと工夫をして、次のようにすれば内容を 見ることができます。
$ echo -n "$IFS" | od -b
0000000 040 011 012
0000003
これは echo の出力を od コマンド(8進数ダンプコマンド)にパイプでつなぎ IFS の内容の8進数で表示させたものです。先頭の数字は入力ファイルの先頭から のオフセット番地で、それに続く「040 011 012」が IFS の内容となります。

$#

シェルスクリプトが実行されるとシェル変数「$#」にはコマンドラインに与えられ た引数の個数が格納されます。ユーザが入力した引数の数が正しいかを調べたり、 引数の数を見て処理を分岐させるときなどに利用します。また、「set」コマンド でレコードからフィールドを切り出したときには、切り出されたフィールド数が格 納されます。

位置パラメータ ($1〜$9, $0) と shift

シェルスクリプトも他のコマンド同様に引数を受け取るとができます。シェルはコ マンドラインを処理した後でこのために用意された特殊なシェル変数に与えられた 引数値を格納してからスクリプトを実行します。これらの特殊なシェル変数は位置 パラメータと呼ばれ、ドル記号「$」に続く1文字の数字で表現します。$1、$2, .. $9 はそれぞれ第1、第2, .. 第9引数に対応します。

位置パラメータはドル記号「$」に続く1個の数字で表されますので9個を超える 位置パラメータを直接参照することはできません。もし 10 個目、あるいはそれ以 降を参照するには「shift」コマンドを使います。「shift」コマンドを実行すると 位置パラメータの内容が左に1つシフトし、$1 に $2 の内容が、$2には $3 の内容 が、.. と位置パラメータの内容が順次左に送られ、$9 にいままで隠れていた 10 番目の引数の内容が入ります。この時に引数の数を表すシェル変数「$#」の内容も 1つ減少します。

「shift」コマンドで位置パラメータをシフトさせると古い $1 の内容は失われて しまいます。もしその後の処理で古い $1 の内容が必要ならば「shift」を使う前 に退避しておかなければなりません。

また、「shift」コマンドに引数「n」を与えることにより一度に「n」回のシフト させることもできます。一般書式は次のようになります。

shift  n
引数の数「$#」がゼロになりこれ以上シフトできないときには、エラーメッセージ 「nothing to shift」が返されます。

$0 にはプログラム名(スクリプト名)が格納されています。これを使えば /usr/bin/compress のように zcat にリンク張り、呼び出された名前によって処理 内容を分けることもできます。

$*

シェル変数「$*」はシェルスクリプトが受け取った $0 以外のすべての引数に対応 します。不特定数の引数を処理する時に利用します。

$@

「$*」と同じくスクリプトが受け取ったすべての引数に対応します。「$*」との違 いは二重引用符で囲み「$@」としたときに位置パラメータの評価をせずにコマンド に渡すことです。もし、二重引用符で囲まなければ「$*」と同じ意味になります。

$?

シェル変数「$?」はシェルが最後に実行したコマンドの終了状態を保持していいま す。直前に実行したプログラムの終了状態を知りたいときに使います。

$$

シェル変数「$$」は現在のシェルのプロセス番号を保持しています。シェルスクリ プト内で一時作業ファイルを作る時に利用します。すべてのプロセスは重複しない 固有の番号で管理されていますから一時作業ファイル(テンポラリファイル)にプ ロセス番号を利用すると他のプロセスが使ってかも知れない作業ファイルとの衝突 を避けることができます。

$-

シェルにセットされているオプションを保持しています。

$!

バックグラウンドで実行された直前のプロセスのプロセス番号を保持しています。

set コマンド

「set」はシェルの内部コマンドです。シェルのオプションを設定・解除する機能だ けでなく、1つのレコードからシェル変数「IFS」で区切られたフィールド取り出し、 それを位置パラメータに代入する機能も持っています。

「set」によるシェルオプションの設定・解除機能は起動時にオプションをコマンド ラインで指定したのと等価です。さらにスクリプト内の任意の位置でオプションの 設定や解除ができますのでスクリプトをデバッグするときに必要な部分だけの実行 状態を監視することもできます。

位置パラメータへの代入機能は「set」の引数として与えられた文字列(レコード) から「IFS」を区切としてそれぞれのフィールドを切り出し、$1〜$9 .. に格納し ます。この機能はユーザが位置パラメータに値を代入できる唯一の方法で、シェル スクリプト内で多用されます。ひとつの例を見てみましょう。

$ date
Wed Jul 29 13:42:25 JST 1994
$ set `date`
$ echo $# $1
6 Wed
$ echo $6 $2 $3
1994 Jul 29
$ shift
$ echo $# $1
5 Jul
$ 
「set `date`」以降の部分に注目してください。date が逆引用符で括られていま すので「set」への引数として date コマンドの出力が渡されています。シェルの 「IFS」はホワイトスペース文字ですから「set」は date コマンドの出力をスペー スで区切って位置パラメータに代入します。dateコマンドをスペースで切った時の フィールド数は6個ですから、$1、$2、.. $6 に次のように格納されます。
  Wed  Jul  29  13:42:25  JST  1994
  $1   $2   $3     $4      $5   $6
そしてシェル変数「$#」には切り出したフィールド数がセットされているのが次の 「echo $# ..」による表示で確認できます。

「echo $6 $2 $3」の部分ではこれらの中から日付を表わすフィールドだけを抜き出 して表示させています。このときに $4 を使えば時間を表示させることができます。

続いて、「shift」を実行しています。「shift」コマンドは位置パラメータの内容 を左に送りますので、$1 にはいままでの $2 の内容が移動します。同時に、格納さ れているフィールド数が1個減少していることが続いて実行した「echo $# ..」の 結果からわかります。

このように「shift」コマンドで位置パラメータの移動を行なうと、古い位置パラ メータの値は永久に失われてしまいます。もし、あとの処理で必要になるならばユ ーザの責任で事前に退避しておかなければなりません。

「set」コマンドに引数を与えずに使うと、ユーザの環境内に存在するすべてのシェ ル変数が表示されます。

シェルの構文

構文規則

シェルプログラム言語は unix の単純なコマンドを組み合せるための制御機構を備 えており、対話形式のコマンドラインで可能なことはすべて書くことができます。

1つのコマンドは改行文字 (0x0a)、あるいはセミコロン「;」で終結します。そし て、if, while, do, done, .. などのシェルの予約語は必ず行の先頭になければな りません。行の先頭とは改行文字の直後のことを言いますが、セミコロンやパイプ 文字の直後も行の先頭とみなされます。

注釈文

どんなプログラミング言語にも必ず用意されているのが注釈文(コメント文)です。 プログラムの保守を容易にし可読性を高めるために注意点やメモを挿入するのに使 われ、シェルスクリプトの実行には影響を及ぼしません。シェル言語では「#」で始 まる文字以降から行末までが注釈文とみなされます。行頭から始まる場合はその行 全体がコメント行として実行時に無視されます。

また、シェルスクリプト内には何も書かない空白行も許されます。コメント文と組 み合わせて適切に使用すると後日のデバッグや保守が容易になります。

入力文

read 文

「read」文は標準入力から1行を読み込み、引数として与えられたシェル変数リス トに順次代入します。

read val_list
この時に、読み込む行の先頭にある「IFS」で指定された空白文字は無視されます。 標準入力が端末のキーボードならば、スクリプト内でユーザからの1行入力に使 うことができます。リダイレクトやパイプなどにより標準入力が切り換えられてい るならばそこから1行を読み込むことになります。

「read」の引数がシェル変数リストならば「IFS」で区切られた語句がそれぞれに 代入されます。もし、読み込んだ1行から分解した語句の数よりもシェル変数名リ スト「val_list」に列記された数の方が少なければあふれた語句は変数名リストに ある最後の変数にまとめて代入されます。「read」文の終了状態は EOF を検出し ない限りゼロ(真)です。

条件判断と分岐

if 文

シェル言語もほとんどのプログラム言語と同様に条件判断のための「if」文を持っ ています。「if」文は1つ以上の条件をテストし、その結果に基づいてプログラム の流れを分岐させます。

「if」文の一般書式は次のようになります。

if cond    あるいは、  if cond
then	                   then
  commands                   commands
  ..                         ..
else                       elif cond
  commands                 then
 ..                       commands
fi                           ..
                           else
                             commands
                             ..
                           fi
この文では「cond」の位置に書かれたコマンドの終了状態(実行結果)を調べて終 了状態がゼロ(真)の場合には「then」節が、そうでない場合には「else」節が実 行されます。もし必要がなければ「else」節は省略することができますが、「if」 文の終了を意味する予約語の「fi」は省くことができません。なお、右側の書式で は「elif」節のネストができます。

終了状態がゼロ(真)の時に「then」節が実行されることは他のプログラミング言 語からすれば逆の印象を受けるかもしれません。unix では、あるコマンドが終了 するとその終了状態 (exit status) を表す数値がシステムに返されます。これは そのプログラムが正常に実行・終了できたかどうかを示すもので、正常に終了した 場合はゼロ(真)が返されます。もし、終了状態が非ゼロ(偽)ならば何らかの原 因で、例えば引数の数が適切でなかったとか、プログラムがエラーを検出したとか .. 等々、異常が起きたと考えられます。ですからコマンドが正常に終了したなら ば「then」節に分岐、と考えればわかりやすい思います。

test コマンド

初めて unix に接した時、使用目的にとまどうコマンドの1つです。これはシェル 内部に組み込まれたものではなく、 unix の一般コマンドの1つですが、シェルス クリプトで条件判断を行なう上で避けて通れませんから少し説明しておきます。

「test」コマンドは与えられた引数を条件式として評価し、その結果が真の時は終 了状態にゼロ(真)を、偽の場合はゼロ以外の値を返します。「test」コマンドは 下記のような文字列、整数、ファイル状態等について多くの条件式を評価をするこ とができます。さらにそれらの論理演算を組み合わせてより複雑な評価もできます。 ただし、評価のショートカットは行われません。(ショートカット:AND や OR な ど論理演算の評価をする場合、例えば AND にあっては前の項が FALSE であれば、 後ろの項の結果にかかわらず全体の評価は FALSE になる。このような時には、後 ろの項の評価をしないことがあります。これをショートカットと言います。)

文字列評価式:
  str1 = str2        文字列 str1 と str2 は一致する
  str1 != str2       文字列 str1 と str2 は一致しない
  -n str             文字列 str は空 (null) でない
  -z str             文字列 str は空 (null)

数値評価式:
  int1 -eq int2      整数 int1 と int2 は等しい (int1 == int2)
  int1 -ge int2      整数 int1 は int2 以上である (int1 >= int2)
  int1 -gt int2     整数 int1 は int2 よりも大きい (int1 > int2)
  int1 -le int2      整数 int1 は int2 以下である (int1 <= int2)
  int1 -lt int2      整数 int1 は int2 よりも小さい (int1 < int2)
  int1 -ne int2     整数 int1 と int2 は等しくない (int1 != int2)

ファイル評価式:
  -d file            file はディレクトリである
  -f file            file は通常ファイルである
  -r file            file は読み出し可能である
  -s file            file の長さは0バイトではない
  -w file            file は書き込み可能である
  -x file            file は実行可能である

論理演算子:
  !                  直後に続く条件評価式の結果を否定する 
 -a                 2つの条件評価式の論理積(and)をとる
 -o                 2つの条件評価式の論理和(or)をとる

「:」コマンド

コロン「:」で表されるこのコマンドは引数を評価するだけで終了状態にゼロ(真) を返します。何もしないコマンドは存在価値も無いと思われそうですが、シェル言 語にとっては「何もしない」コマンドの必要性がしばしばあります。次の例にある 「if」文や「while」文と組み合せた使い方はその典型です。

  if cond ;then         while  :
     :                   do
  else                     commands
     commands	         done
  fi}
また、「:」コマンドでは引数の評価が行なわれますのでシェル変数を検査させる ためにも使われます。「ハノイの塔」に具体例を上げておきます。

&& と ||

「&&」と「||」は、その左側に置かれたコマンドの実行結果を見て右側に置かれた コマンドを実行させるものです。「&&」は command1 の終了状態がゼロ(真)の時 に command2 を実行します。一方、「||」は command1 の終了状態が非ゼロ(偽) の時に command2 を実行します。終了状態は最後に実行されたコマンドのものにな ります。それぞれの一般書式と「if」文で書いた等価式は次のようになります。

command1 && command2  <==>  if command1; then command2; fi
command1 || command2  <==>  if !command1; then command2}; fi

case 文

「case」文は1つのシェル変数値を評価し、同じパターンが見つかったならば1つ あるいはそれ以上のコマンドを実行させます。一般書式は次のようになります。

  case $val in
  pat1)  commands
         ;;
  pat2)  commands
         ;;
  ..
  patn)  commands
         ;;
  esac
ここでの処理はシェル変数 val の値と pat1, pat2, .. patn と連続して比較し、 一致したものが見つかるとそこから2個の連続したセミコロン (;;) までの間に書 かれたコマンド群を実行します。もし、一致するものが見つからなかった場合は何 もしません。最後の easc は case 文の終りを意味し省略することはできません。

シェルでファイル名の置き換えに用いられる「*」などのメタ文字も case 文の比 較対象 (patn) として使うことができます。また、シェルではパイプ記号として用 いられる垂直バー「|」を使うと複数パターンの論理和 (or) を取ることができます。

ループ制御

for 文

一組のコマンドを指定された回数だけ実行するのに使われ、一般書式は次のように なっています。

  for val in arg1 arg2 .. argn
  do
    commands
    ..
  done
do と done に囲まれたコマンド群がループの本体で、これらのコマンド群は in の 後ろにならべられた引数の数だけ繰返し実行されます。このループが実行されると 最初に arg1 の内容が、次に arg2 の内容が .. 、と順次 val に参照されながら in に続くリストの中身が空になるまでループ処理が継続します。つまり、in の後 ろに n 個の引数があったならば n 回ループが実行されることになります。in に 続く引数に 「*」「?」「[ ]」などのメタ文字が含まれる場合にはシェルにより展 開されてから for ループが実行されます。

シェル変数「*」はスクリプトに渡されたすべての引数に対応するために for 文と 組み合わせて使うことができます。しかし、実際の使用に当っては少し注意が必要 です。次に示すスクリプトはコマンドラインに入力された引数を1行に1個づつ表 示させるものですが、時として期待を裏切ります。

echo "Number of arguments: $#"
for i in *
do
  echo $i
done
このスクリプトをfor.shとして引数を変えて実行してみましょう。
$ for.sh argA argB argC
Number of argumets: 3
argA
argB
argC
$ for.sh 'argA argB' argC
Number of argumets: 2
argA
argB
argC
$
最初の引数の時には正常です。しかし、次の単一引用符でスペースを含む引数をシ ェルから保護したときには不思議なことが起こっています。argA と argB は単一 引用符で括られていますから引数は2つとなります。事実、与えられた引数の数を 示すシェル変数「$#」は間違いなく「2」と表示されています。なのになぜか3行 に出力されてしまいました。

種あかしはこうです。「argA argB」はシェルから保護されて1つの引数としてス クリプトに渡されます。しかし、保護されるのはコマンドラインでのことであり、 引数としてスクリプトに渡った時にはシェルにより単一引用符が外されています。 for 文で「*」が展開された時に、in 以降のリストには argA と argB を1 つにしている単一引用符がありません。ですから、

for i in argA argB argC
となってしまい、ループは3回実行されることになります。これを回避するために、 for 文には in 以降のリストを持たない特別な記述が許されています。for.sh 2 行目の in 以降を省略し、
echo "Number of arguments: $#"
for i
do
  echo $i
done
としたものです。これで試してみると
$ for.sh 'argA argB' argC
Number of argumets: 2
argA arcB
argC
$
となるはずです。あるいは、特殊なシェル変数「$@」を使うもの1つの方法です。 シェル変数「$*」はコマンドラインでの引用符が外された状態の引数を $1, $2, $3 .. として持っています。この「$*」の代わりに「$@」を使うとスクリプト内 で $1, $2, $3 .. として置き換えられるために「$*」を使ったときのような不都 合は起こりません。この時には必ず、二重引用符で囲み「$@」としなければなりま せん。さもないと、「$*」とまったく同じように展開されてしまいます。

while 文

ある条件が満たされている間ループを実行します。一般書式は次のようになります。

while cond
do
  commands
  ..
done
まず最初に cond コマンドが実行されその終了状態がテストされます。もし終了状 態がゼロ(真)ならば do と done で囲まれたコマンド群を実行します。そして、 もう一度 cond が実行されて終了状態が検査されます。もしゼロ(真)ならば再び do〜done のコマンド群が実行され、非ゼロ(偽)ならば done の次のステップに進 みます。

for.sh と同じ働きを while 文で実現した例を示します。ループ条件の判断には testコマンドを使って引数の数を検査しています。

echo "Number of arguments: $#"
while test $# -gt 0
do
  echo $1
  shift
done

until 文

「until」文は「while」文とは反対に終了状態が非ゼロ(偽)である間ループが実 行されます。期待する現象が起こるのを待って処理を行う場合などに使われ、一般 書式は次のようになります。

until cond
do
 commands
  ..
done
unix のようなマルチタスクOSで複数のプロセスが1つのファイルを共有する時には 排他制御をしなければなりません。このためのロックファイルをスクリプトで作る ときに次のような使い方をします。
until (ロックファイルを作る)
do
  sleep 30
done
「until」の条件文で排他制御用のロックファイルを作ろうとします。既にロック ファイルが存在していたならば、そのファイルは他のプロセスで使用中ですから作 成に失敗して非ゼロ(偽)が返されます。ファイルへのアクセス権が得られなけれ ば do〜done が実行されます。この例では 30 秒間隔で再試行を行ない、ロックフ ァイルが作れたならば次の処理に進みます。

break 文

ループ処理をしている時に、ある状態になったならばすぐにそのループから脱出し たい時があります。シェル言語ではこのために「break」文が用意されています。 「break」文が実行されると制御はただちにそのループの外に移り、「done」の次 のステップから実行されます。さらに、ループがネストしており、複数のループを 一気に脱出したい時には「break」に引数として脱出したいループの数を指定しま す。一般書式は次のようになります。

break n
引数「n」が省略された場合には1として、最も内側のループから脱出します。

continue 文

「continue」はある条件が満たされているときなどにループ内のコマンド群をスキ ップするために使われます。「continue」文はそれ以降のコマンド群をスキップす るだけでループは継続条件が満さたれている限り続けられます。break 文と同様に 引数をつけることにより「n」番目のループから実行させることができます。 一般書式は次のようになります。

continue n

ループ文のリダイレクトとパイプ処理

「for」,「while」,「until」のループ制御文は do〜done とで囲まれる複数のコ マンドを1つのコマンドのように実行しますので、ループ全体の入出力をリダイレ クトしたり、パイプに接続することができます。バックグラウンドでの実行もルー プ全体が対象となります。

次のリストは「for」文の標準出力をリダイレクトする例です。

for i in beer carrot ell
do
  echo $i
done >food
ループ全体が1つのコマンドとして扱われますからリダイレクト文字「>」はルー プの終了を表す「done」の後書きます。この時のリダイレクト対象はループ内で標 準出力に書き出すものすべてが対象となります。しかし、ループ内でリダイレクト 先が明示されているものはループ全体のリダイレクトに優先されます。

例えば、上のリストで「done >food」に優先して /dev/tty にリダイレクトしたい ものがあれば次のようにします。

for i in beer carrot ell
do
  echo "I like $i" >/dev/tty
  echo $i
done >food
同様に下記のようにパイプ記号「|」を「done」に続けて書くことによりループの 出力をパイプに流し込むこともできます。
for i in beer carrot ell
do
  echo $i
done | food
「or」や「while」文などのリダイレクト処理はカレントシェルではなく、サブシェ ルで実行されることに注意してください。このことを知らないとバグでもないのに おかしな現象に悩まされることになります。次のスクリプトは自分自身を行番号付 きで表示するものです。
n=0
while read line
do
  n=`expr $n + 1`
  echo "$n: $line"
done < $0
echo "total line= $n"
これを myself.sh と名付けてして実行してみてください。各行の先頭にふられた 行番号は正しく表示されていますが、最後に「total line= 0」と表示され期待し た結果が得られません。種明しをするとこうなります。ループ文の内側で行番号を 表示するために用いられているカウンタ「n」はサブシェル側の変数で、ループの 外側にある変数「n」はカレントシェルのものです。(この myself.sh を起動した シェルから見ると子、孫の関係になります)同じ名称の変数でもループの内と外で はまったく別物ですから、「while」ループの外側にある変数「n」はスクリプトの 最初で初期化されたままになっています。

ここで混同しないでいただきたいのですが、ループ文がサブシェルで実行されるの はスクリプト内でリダイレクト処理を指定したときだけです。リダイレクトを指定 しないループ文はカレントシェルで実行されます。スクリプト myself.sh を次の ように書き直して、コマンドラインから「myself.sh < myself.sh」とすると行数 を数える変数「n」は期待する値を取ることを確認してください。

n=0
while read line
do
  n=`expr $n + 1`
  echo "$n: $line"
done
echo "total line: $n"
さらに、入力をパイプから読み込むよう、次のように書き直して変数「n」の値を 調べてみてください。
n=0
cat $0 | while read line
do
  n=`expr $n + 1`
  echo "$n: $line"
done
echo "total line: $n"

ループ文のバックグラウンドでの実行

ループ処理全体をバックグラウンドで実行させるにはループの終了を示す「done」 の後ろにバックグラウンドへ送る指示のアンパーサンド「&」を付けます。次のも のは複数のソースからなるプログラムリストをバックグラウンドで連続紙に印刷す る例です。

for i in *.[hc]
do      pr -l66 -w132 $i | lpr
done &
算術演算

シェルプログラミングで算術演算を行うには次のような書式で「expr」コマンドに 算術式を引数として与えます。

expr val1 算術演算子 val2
「expr」の算術演算子には以下のようなものが使えます。
算術演算子         意   味
--------------------------------------------------------------------
( arguments )      括弧で囲んで優先順位を明示する
str : regexp       文字列 str と正規表現 regexp を比較する(一致演算子)
val1 * val2        積 val1 x val2
val1 / val2        商 val1 / val2
val1 % val2        余 val1 mod val2 (剰余)
val1 + val2        和 val1 + val2
val1 - val2        差 val1 - val2
val1 op val2       op に <, <=, =, != >=, > を用いて比較演算を行なう
                   条件成立ならば1を、不成立ならば0を返す
val1 & val2        val1, val2 共に0でなければ val1 の値を、それ以外は0を
                   返す
val1 | val2        val1 が0でなければ val1 を、val1 が0ならば val2 を返す
一致演算子「str : regexp」は左側の文字列「str」と右側の正規表現「regexp」 を比較して一致した文字数を返します。もし一致なかったならばゼロが返ります。 この時の正規表現には「ed」と同じ記法が使えますし、「ed」と同じ記憶メカニ ズム「\(正規表現\)」を使って文字列の中から正規表現に一致した部分を抜き出す こともできます。次はその簡単な例です。
$ val=Yokohama,221
$ city=`expr $val : '\([^,]*\)'`
$ zip=`expr $val : '[^,]*,\(.*\)'`
$ echo $zip $city
221 Yokohama
$
シェル変数「val」には「Yokohama,221」のように都市名と郵便番号がカンマ「,」 で区切られて入っているものを、「expr」の一致演算子を用いて都市名と郵便番号 を分離しています。一致演算子「:」の左側にある文字列から右側に書かれた正規 表現に一致している部分を結果として返します。つまり、都市名としては文字列中 のカンマの前までを、郵便番号としてはカンマ以降の部分を「expr」の処理結果と して返しています。

正規表現についてはここではふれませんが、ed などの文献に詳しい説明が用意され ています。

実行制御

exit 文

シェルはスクリプトの最後(End OF File)に達すると自動的に終了しますが、 「exit」を使うことにより任意の位置で実行を終了させることができます。一般書 式は次のようになります。

exit n
引数「n」ではシステムに返す終了状態を指定します。通常、コマンドが正常に終了 した時はゼロ(真)を返す習慣になっています。もし、引数「n」が省略された場合 には「exit」の直前に実行されたコマンドの終了状態が返されます。

なお、ユーザ端末からコマンドラインで「exit」を実行すると現在のシェルを終了 させることになります。もし、それがログインシェルならばログアウトと同じ結果 となります。

exec 文

シェルに代わって引数で指定されたコマンドを実行しますが、新しいプロセスは作 られません。書式は次のようになります。

exec argument
シェルは「exec」の実行にあたり現在のファイルをクローズし、新しいファイル をオープンしますのでそれ以降の入出力を引数で指定したものに切り換えることが できます。この機能を使って「exec」文以降の標準入力を「file」に切り換えたい ならば
exec <file
のようにします。同様に標準出力やエラー出力を「file」に切り換えたいのなら ば次のようにします。具体的には「配列」の例を参照してください。もし、「exec」 の引数に普通のコマンドが混じっていたならば、それが実行されるだけです。
exec >file
exec 2>file

eval 文

引数をシェルの入力として解析してからコマンドとして実行します。引数を実行す る前にコマンドラインの解析が1度行なわれますから、結果としてコマンドライン を2回走査させることができます。書式は次のようになります。具体的な例は 「配列、ハノイの塔」にあります。

eval argument

wait 文

ユーザが実行している子プロセスの終了を待ち、終了状態を保存します。書式は次 のようになります。

wait n
引数「n」には待ちたい子プロセスの識別番号(プロセスID)を指定します。待つ べきプロセスID「n」を省略するとその時点で走っているすべての子プロセスの終 了を待ちます。なお、「wait」の終了状態は待っていた子プロセスの終了状態その ものです。

バックグラウンドで実行させた子プロセスのIDを知るにはシェル変数「$!」を参照 します。

trap 文

シェルスクリプトを書くときには実行中になんらかの原因で停止することも念頭に 置かなければなりません。停止する要因としてはユーザのDELキーよる割り込みとか、 異常終了、キルシグナルが送られたとか、さまざまなものが考えられます。

この時にただちにシェルスクリプトを終了させてしまうとまずい場合があります。 例えば、一時作業ファイルを作って処理をしていて後始末をせずに終了してしまっ たのでは作業ファイルがディスクのゴミとして残ってしまいます。また、排他制御 のためのロックファイルを削除せずに終了したならば困ったことになります。この ような不都合を回避するために「trap」命令を使います。

「trap」はあるシグナルを受信した時になすべき仕事を指定するのに使い、書式 は次のようになります。

trap command signal
command は「signal」に指定されたシグナルリストのいずれかを受信したときに実 行されるコマンドです。「signal」を受信したときに実行するコマンドが2つ以上 の場合にはそれらを引用符で囲まなければなりません。signal は一連の番号で表さ れ、unix で使われる主なものは次のようなものです。
  0 シェルから脱出するときに必ず発生する 
  1 ハングアップ。通常は回線のキャリアが切断すると発生する
  2 通常DELキーが押されたときに発生する端末割り込み
  3 クイットシグナル。プロセスを停止させコアダンプを行なう
  9 キルシグナル。すべてのプロセスで無視も受信もできない
 15 kill コマンドにより発生するシグナル
次のリストは「trap」文を使って一時作業ファイルを削除し、スクリプトを終了さ せる例です。
prog=`basename $0`
tmpfile="/tmp/${prog}$$"
trap 'rm -f $tmpfile; exit 1' 2
any-command >$tmpfile
..
一時作業ファイル名には他のプロセスとの衝突を避けるためにプロセスIDを含めた ものが使われます。ここでもその慣例に従ってシェルスクリプト名とプロセスIDが 格納されているシェル変数「$$」を使って一時作業ファイルを作っています。そし て「trap」文です。ここではシグナル2を受け取ったときに 「'rm $tmpfile; exit 1'」が実行されるように設定します。なお、「trap」の設定 は一時作業ファイルが作られる前に行なわなければ意味がありません。

シグナルを無視したい時には次のように実行すべきコマンドを書く部分を引用符で 囲んで「ヌル」にします。

trap '' signal
しかし、「コマンドを書かなければ無視される」と早合点し「trap」文の第1引数 を省略して
trap signal
としてはいけません。このようにすると「signal」を受け付けたときの処理をデフ ォルトに再設定してしまいます。例えばシグナル2ならばシェルスクリプトを停止 させる標準処理を行ないます。

なお、ユーザがあるシグナルを無視するように設定すると、そこから起動されるサ ブシェルも(シェルスクリプト)そのシグナルを無視します。しかし、あるシグナ ルを受信したならば特定の処理を行うように設定した場合には、その処理内容はサ ブシェル側にはいっさい伝わりません。サブシェル側は該当シグナルに対して既定 の処理を行うだけです。

シェルスクリプトのデバッグ

シェルはさまざまな実行環境を作り出すために幾つかのオプションを持っています。 スクリプトのデバッグ用としては「-v」、「-x」の2つオプションを使うことがで きます。

「-v」はスクリプトからコマンドを1行読むごとに表示させるためのオプションで、 構文のチェックに利用できます。オプションを設定するにはシェルスクリプト内に

set -v
の1行を追加するか、あるいは次のようにオプションを指定してシェルスクリプト 走らせます。
sh -v script
「-x」オプションはコマンドを実行するたびにそのコマンド名と引数を「+」に続 いて表示するものです。引数はシェルにより展開されたものが表示されますので実 行状態をトレースすることができます。これらのオプションをまとめて、あるいは 設定されているものだけを解除するには
set -
とします。もし、個別に解除したければ
set +v
などとします。なお、現時点でシェルに設定されているオプションを知りたい場合に はシェル変数「$-」を調べます。

実際に使われることは少ないですが「-n」,「-u」なども役に立つかも知れません。 「-n」は読み込んだコマンドの実行を禁止させるためのオプションです。予想もし ないバグで大切なファイルを削除してしまうなどの被害を未然に防ぐことができま す。カレントシェルで「set -n」とすると EOF (End Of File) を入力するまでそ の端末からの操作ができなくなります。「-u」オプションを設定すると値が格納さ れていないシェル変数を参照しようとした時に「unset variable」のエラーメッセ ージが表示されスクリプトが停止します。

それといまさらでしょうが、ソースデバッグもお忘れなく。結局、これに勝るデバッ グ手法はないようです。

シェルスクリプトの実例

短いシェルスクリプトを例に取りながら今までの復習をしてみましょう。

挨拶

きちんと挨拶されると気持ちがいいものです。そこで unix にも login したとき、 ご主人様にちゃんとご挨拶ができるように教えることにしましょう。

#!/bin/sh

`date`
IFS=:
set $4

case $1 in
0[6-9]|1[0-1])
  echo "Good morning. Sir"
  ;;
1[2-7])
  echo "Good afternoon. Sir"
  ;;
1[89]|2[01])
  echo "Good evning. Sir"
  ;;
*)
  echo "GO TO Bed!! :-)"
  ;;
esac
解説

ご挨拶スクリプトは最初に date コマンドの出力を set の引数として与え、スペ ースで区切られたフィールドを切り出します。続いて「IFS」をコロンに設定して、 いま切り出した第4フィールドをさらに分割します。ここまでの操作で date コマ ンドの出力から時間を表す部分だけを取り出します。

そして、その時間を見て表示する挨拶メッセージを「case」文で選択します。「お はよう」「こんにちは」「こんばんわ」のどれも言えないような時間帯にはあなた の健康を気づかって「寝た方がいいんじゃない?」となります。

このスクリプトをあなたの .profile に書いておくとloginした直後に挨拶メッセ ージを見ることができます。

演習問題

さて、このスクリプトで挨拶メッセージを表示する時にあなたのログイン名を用い て「Good morning. taroh」などと表示させるにはどのように修正すればよいか考 えてみてください。(メッセージ内にログイン名を直接埋め込むのは論外です)

配列(表引き)

Bシェルには配列操作のコマンドが組み込まれていませんが、シェル変数とコマン ドを上手に操ることにより配列に格納されている値の参照と等価な仕事を実現でき ます。

#!/bin/sh

exec 3<&0 <month.name
i=0
while read k month
do
  i=`expr $i + 1`
  eval M_$k='"$month"'
done
exec 0<&3 3<&-
while test $i -gt 0
do
  eval echo \$i -- \$M_$i
  i=`expr $i - 1`
done
ファイル month.name の内容
--------------------------
3 March
1 January
11 November
2 Feburuary
4 April
12 December
6 June
8 August
9 Sepember
7 July
10 October
5 May
解説

このスクリプトは大きく分けて配列として扱うシェル変数へ別ファイルから読み込 んだデータをセットする部分 (5〜9行目) と、それを表示する部分 (11〜15行目) から成っています。ここでは 12 カ月の各月に対応する名称を書いた month.name というファイル用意して使うことにします。

このスクリプトは最初に「read」文を使いファイルからデータを読み込みますが、 この時に 5 行目から始まる「while」文の入力を month.name にリダイレクトして

while read k month
do
    ......
done < month.name
としてはいけません。理由は「ループのリダイレクト」で説明したように「while」 文はサブシェルで実行されるためです。このためループ内のシェル変数はループ の外側では参照できません。そこで3行目の処理となるわけです。「exec」と「リ ダイレクト」とを併せて考えてください。3行目の「exec 3<&0<month.name」はま ずファイルディスクリプタ「0」の入力をファイルディスクリプタ「3」に変更しま す。ファイルディスクリプタ「0」はシェルの標準入力ですからこの処理で標準入 力がファイルディスクリプタ「3」に保存されることになります。

続いてファイルディスクリプタ「0」で month.name を読み込み用にオープンしま すので、それ以降の標準入力はファイル month.name となります。これでカレント シェルで month.name の内容を「read」で読むことができるようになりました。

「read」で読み込んだものを配列状態でシェル変数に格納する部分が8行目です。 month.name から1行目の「3 March」を読み込んだとします。この内容は「read」 文によりシェル変数 k に「3」、month に「March」が取り込まれ、8行目に来ま す。「eval」はシェルが引数を2度評価するのと等価な働きをします。1度目の 評価では「$k」の置換と右辺の単一引用符が外されますので

M_3="$month"
となります。続く2度目の評価で右辺の「$month」が置換されます。最終的に 8 行目は次のようになり、シェル変数「M_3」への代入操作が実現できます。
M_3="March"
この処理は month.name が EOF になるまで繰り返されます。

「while」ループから抜けたならば 10 行目の「exec」文でもう一度標準入力を切 り換えます。先ほどファイルディスクリプタ「3」に保存しておいた元の標準入力 を復帰させ、不要になったファイルディスクリプタ``3''をクローズします。これ で 3 行目で標準入力を切り換える前の状態に戻ったことになります。

最後はシェル変数を配列の添字を移動してアクセスする動作を真似て month.name から読み込んだ内容を表示させます。シェル変数「$i」は読み込んだ項目数を保持 していますので、これがゼロになるまで減算しながら表示を行ないます。

演習問題

13行目の「eval」でのシェル変数の変遷をたどってみてください。

ハノイの塔

C 言語を憶えたてのころに見たことがある方もたくさんいると思います。再帰呼び 出しのサンプルとして有名な「ハノイの塔」をシェルスクリプトで書いたものです。 速度は期待できませんが再帰処理さえも簡単にこなしてしまうシェル言語には目を 見張るものがあります。

再帰による「ハノイの塔」の解を求めるスクリプトそのものは簡単なものですが、 このことにこじつけて他のことも併せて説明しようと欲張ったので行数の多いスク リプトになってしまいました。

#!/bin/sh
# Tower of HANOI

#: ${1?Parameter unset}
set -u

sub='./hanoisub'
export sub
trap 'rm -f $sub;exit' 0 1 2 3 15

cat > $sub << 'EOF'
eval X=\`expr 6 - `expr $2 + $3`\`
if test $1 -gt 1; then
  $sub `expr $1 - 1` $2 $X
fi
echo "Move disk #$1 from  $2 to  $3."
if test $1 -gt 1; then
  $sub `expr $1 - 1` $X $3
fi
EOF

chmod +x $sub
$sub $1 1 2

解説

「ハノイの塔」スクリプトの本質は 23 行目の「$sub $1 1 2」で、「$1」にはス クリプトの第1引数としてに与えられた円盤の枚数がセットされています。このあ とは「$sub」が再帰呼び出しを重ねて解を表示してきます。

サブルーチン「$sub」の所在は 11〜20 行目のヒアドキュメント部分です。サブル ーチンをメインのシェルスクリプトに含めておき、実行するときにだけサブルーチ ンをディスクに展開して使用し、終了時には削除するようにしてあります。

では 7 行目から見て行きましょう。ここは前準備部分で、サブルーチンとするシェ ルスクリプト名を定義し、それを「export」宣言(8行目)しサブシェル側に伝わ るようにしておきます。続いて「trap」文を使って終了する時には必ずサブルーチ ンとしてディスクに書き出したスクリプトを削除するようにしておきます。これを スクリプトの最後に(24 行目あたりに)削除命令を入れておいたのでは DEL キー による中断などの時にゴミを残してしまいます。

11行目「cat >$sub <<EOF」のヒアドキュメント部分で 12〜19 行目をサブルーチン として次のような内容をディスクに書き出します。

eval X=\`expr 6 - `expr $2 + $3`\`
if test $1 -gt 1; then
  $sub `expr $1 - 1` $2 $X
fi
echo "Move disk #$1 from  $2 to  $3."
if test $1 -gt 1; then
  $sub `expr $1 - 1` $X $3
fi
さて、「$sub」を「export」宣言した意味がお分かりでしょうか。このサブルーチ ンにも同じシェル変数が3行目と7行目に使われています。もし、「export」され ていなければこの変数はサブシェル側では未定義となりますので、シェルは 「`expr $1 - 1`」の結果をコマンドと解釈して誤動作のすることになります。

サブルーチン「$sub」を切り出したならば実行属性を与えてスクリプトとして実行 できるようにします。そして、柱に通してある円盤の数を第1引数として「$sub」 を起動します。解を求める作業が終了すれば戻ってきます。

ディスクに書き出したサブルーチンの後始末は9行目の「trap」が請け負っていま す。実際には 23 行目の処理が済みスクリプトを終える時点でシグナル「0」が送 られてきますから「trap」の第1引数で指定した「rm -f $sub」が実行されます。

4行目の「set -u」「スクリプトのデバッグ」で説明したスイッチで、未定義の変 数が参照された時にエラーメッセージを表示してスクリプトを停止させるものです。 ここでは引数なしで hanoi が呼び出されたときの処理に利用しています。

さて、5行目に見慣れないものが書かれています。これはシェル変数置換の一種で、 第1引数「$1」が未定義ならばエラーメッセージ「Parameter unset」を表示して スクリプトを停止させます。表示させたいメッセージを書かずに「:$1?」とすれ ばシェルが持っているエラーメッセージを表示します。この時にヌルコマンドとし てのコロン「:」を行頭に置くことを忘れないでください。([ヌルコマンド」参照)

クエスチョンマーク「?」以外にも、マイナス記号「-」や等号「=」も使うことが でき、それぞれ次のような意味を持っています。

:${val?msg}  シェル変数 val が未定義ならば msgv を表示してスクリプトを停止
:${val-str} シェル変数 val が未定義ならば str を代入する
:${val=str}  :${val-str} に同じ。但し、位置パラメータには適用不可

演習問題

例として取り上げたハノイの塔のスクリプトは起動された本体からサブルーチン用 のスクリプトを展開し、それを呼び出しています。解を求めるために2つのスクリ プトが必要になることに変わりありません。そこで、サブルーチンを用いずに1つ のスクリプトだけで解を求めるものを作ってみてください。

シェルスクリプトによる簡単なデータベース

概要

シェル言語を使って特別なプログラムによらずにシェル言語と実装されているコマ ンドだけで少し実用的なスクリプト「超簡易文献データベース Cabinet」を作って みましょう。

キャビネットから物を捜したり、出し入れしたりというものを模倣したものです。 実際のキャビネットに収められている物は文献やビデオなど多種多様ですから、違 った種類のものを詰め込むような設計にもできます。ここでは、初心者にも分かり やすいようにできるだけ簡素な構成とするために文献検索用に目的を限定します。

サンプルとした文献データにはこれから unix を勉強する際の手助けになる資料と して使えるものを用意しました。

データ形式

扱うデータはすべて unix の標準的なテキストファイルです。テキストファイルは 人間も読めますが、ユーザが扱いやすいデータ形式と、unix のツール類が扱いや すい形式は違います。

人間は極めて融通がききますから(考えようによってはいいかげんな)、1つのデ ータが数行になろうが、コーヒーをこぼしたシミがあろうが、必要な情報だけを簡 単に拾い出すことができます。一方、unix のツールの多くは1行を処理単位とす る時が最も効率良く動作します。どれほど高度な処理ができる人間でも文字がベタ 書きされていたのではうんざりします。1つの文献を「著者」「標題」「出版社」 「出版年」「ISBN」からなるデータの集合で表すとします。

さて、あなたは (a) と (b) のどちらが扱い易いでしょうか。

(a)  Andrew S.Tanenbaum
     OPERATING SYSTEMS DESIGN AND IMPLEMENTATION
     Prentice-Hall
     1987
     0-13-637331-3

(b)  Andrew S.Tanenbaum:COMPUTER NETWORKS:Prentice-Hall:1981:0-13-165183 .....
     Andrew S.Tanenbaum:OPERATING SYSTEMS DESIGN AND IMPLEMENTATION:Pren .....
よほどのアマノジャクでない限り人間が扱い易いデータ形式は (a) の方だろうと 思います。一方、(b)は unix 上のコマンド群が扱いを得意としているものです。

そこで、1つの文献データの入力は (a) の形式で著者、表題、出版社、出版年、 ISBN をそれぞれ1行に書き、5行で1つの文献として入力します。そして、それ ぞれのデータは1行以上の空白行で区切ることとします。これを(b)のデータ形式 に変換して検索用のデータとして蓄えることにします。このデータをスクリプト で検索し、目的のデータが見つかったならば再度 (a) のような構成にして人間に 提示すれば良いことになります。

Cabinet の部品

それではこのためにどのような部品を用意すればよいでしょうか。データの入力、 検索、出力と分けて考えてみます。

まず入力されるデータは通常のテキストファイルですから unix 備え付けのエディ タ ed, vi, emacs(mule), ng などから好きなものを使うことにし、(a) から (b) のデータ形式に変換するスクリプトと組み合せることにします。検索には正規表現 が使えるgrepを使いましょう。出力は、データ形式 (b) のフィールドがコロン「:」 で区切られているので「IFS」を切り換えて各々のフィールド取り出し echo で表 示させます。この方針で次のような5つのスクリプトを用意してみました。

1. add - 文献データ追加用のスクリプト
2. upd - 検索用データファイルの更新スクリプト
3. all - 全文献データの表示のスクリプト
4. se  - 文献検索スクリプト
5. cab - メニュー処理

add - 文献データ追加

引数として検索用データに新しい文献データをデータ形式 (a) で書き込んだファ イル名を与えることにより追加できます。もし、引数がない場合はコンソールか ら文献データを1件だけ取り込みます。

#!/bin/sh
# ADD data to the Cabinet File

: ${CABINET?}
ORG=$CABINET
REC="Auther Title Publisher Year ISBN"

if test -z "$CABINET"; then
  exit
fi

if test $# -ne 0; then
  for i ; do
    if test -f "$i"; then
      continue
    else
      echo "Error: \'$i\' dose not exist."
      exit
    fi
  done
  if test -f "$ORG"; then
    cp $ORG ${ORG}.bak
  fi
  for i ; do
    echo >> $ORG
    cat $i >> $ORG
    echo "\'$i\' has been added to the $CABINET Cabinet"
  done
  upd
else
  echo "Type in NEW data"
  for i in $REC; do
    echo -n "$i --> "
    read line
    eval $i=\"\$line\"
  done
  echo '----------------------------'
  for i in $REC; do
    eval echo \"$i\: \$$i\"
  done
  echo '----------------------------'
  echo -n '                Ok? (y/n) '
  read line
  if test "$line" = y -o "$line" = Y; then
    echo >> $ORG
    for i in $REC; do
      eval echo \"\$$i\"
    done >> $ORG
    echo
    echo "This data has been added to the $CABINET cabinet."
    upd
  fi
fi

upd - 検索データの更新

人間による入力データは作業性を考えて複数行で1レコードを構成していますが、 unix のコマンドは1行で1つのレコードを構成するものを扱うように設計されて います。そのための変換を行ない、検索用のデータファイルの更新を行ないます。 このスクリプトは入力用のスクリプト「add」から呼び出されます。

#!/bin/sh
# update cabinet

: ${CABINET?}
RACK=${CABINET}.items
TMP=/tmp/_$$
trap 'rm -f $TMP' 0 2

echo -n "Now, updating $CABINET cabinet. Just a minute, Please!"
verb@lbuf=""
> $TMP
cat $CABINET | while read line; do
  if test "$line" = ""; then
    if test -n "$lbuf"; then
      echo $lbuf >> $TMP
      lbuf=""
    fi
  else
    if test "$lbuf" = ""; then
      lbuf=$line
    else
      lbuf="$lbuf:$line"
    fi
  fi
done
if test -n "$lbuf"; then
  echo $lbuf >> $TMP
fi
echo
sort -fut':' +0.0 -2.0 $TMP > $RACK

all - 全文献の表示

80 桁のコンソールでも読み易いように3行で1件のデータを表示させています。 データの出力先は標準出力ですからパイプで more などのページャにつなげばペー ジ単位で停止させられますし、lpr を指定すればプリンタに出力できます。

#!/bin/sh
# LIST all of the entries in the Cabinet File

: ${CABINET?}
RACK=${CABINET}.items

if test ! -s "$RACK"; then
  echo 'Empty Cabinet.'
  exit
fi

echo "  `wc -l < $RACK` item(s) in your Cabinet."
IFS=:
cat $RACK | while read A B C D E; do
  echo; echo $B
  echo $A
  echo "$C, $D(ISBN:$E)"
done
echo

se - 文献の検索

引数として与えられた文字列の条件を満たす文献すべてを探し出します。検索キー ワード文字列には正規表現が使えます。

#!/bin/sh
# SEarch data in the Cabinet File

: ${CABINET?}
RACK=${CABINET}.items
TMP=/tmp/_$$

trap 'rm $TMP; echo; exit' 0 2

if test $# -eq 0 -o -z "$CABINET" -o ! -f "$RACK"; then
  exit
fi

echo -n "looking for \'$*\' .."
grep "$*" $RACK > $TMP

if test -s "$TMP"; then
  echo "    `wc -l < $TMP` item(s)"
  IFS=:
  cat $TMP | while read A B C D E; do
    echo; echo $B
    echo $A
    echo "$C, $D(ISBN:$E)"
  done
  echo
else
  echo " Sorry, I can\'t find in the $CABINET Cabinet"
fi

cab - Cabinet メニュー

「add」,「upd」,「all」,「se」は部品として作られており、単独で走らせること ができます。初心者にとってはメニューの方がなじみやすいでしょうから、メニュ ー画面で数字で指定することにより5つの作業ができるようにしてみました。

#!/bin/sh
#

: ${CABINET=Book}
export CABINET
OUTPUT=more
OUT=Display
RACK=items

trap 'continue' 2

if test $# -ne 0; then
  if test -d $1; then
    CABINET=$1
  else
    exit
  fi
fi

while true ;do
  echo; echo; echo -n "
  $CABINET Cabinet  .. `wc -l < ${CABINET}.items` item(s)
  ==================================================
    Would you like to:

          1) Search data in the Cabinet
          2) Add data to the Cabinet
          3) List all of the Cabinet
          4) Change output (current: $OUT)
          5) Quit
  ==================================================
                          SELECT (1-5): "
  read command
  echo
  case "$command" in
  1 | s)  echo -n '[SEARCH] Enter Keyword: '
    read line
    if test ! -z "$line"; then
      se "$line" | "$OUTPUT"
      if test "$OUT" = Display; then
        echo -n '>>> Hit ENTER to continue <<<'
        read line
      fi
    fi;;

  2 | a)  echo -n '[ADD] Enter filename: '
    read line
    if test -z "$line"; then
      add
    else
      add "$line"
    fi;;

  3 | l)  echo '[ALL]'
    all | $OUTPUT;;

  4 | c)  if test "$OUT" = Display; then
            OUTPUT=lpr; OUT=Printer
          else
            OUTPUT=more; OUT=Display
          fi;;

  5 | q)  exit;;
  *)      echo "??? \'$command\'";;
  esac
done

Cabinet の使い方

「Cabinet」は初心者の学習用として作った文献検索用のシェルスクリプトです。 使用方法についてはスクリプトを読んでいただければ一目瞭然ですし、それがまた シェルスクリプト理解への近道でしょう。さらに新しい機能を追加するなどして、 実際に手を加えてみてください。それが思い通りに走ったときには楽しさも倍増す ること請け合いです。とはいえ、「とにかく遊んでみたい」というせっかち屋さん のために簡単な説明を用意します。

「Cabinet」本体はデータを追加・更新を行なう「add」、「upd」、検索を担当す る「se」そして全データをダンプする「all」の4つのスクリプトから構成されて います。これらはそれぞれがが独立して走るようになっており、コマンドラインか ら直接起動させることができます。しかし、コマンドラインからは柔軟な使い方が できる反面、シェル変数の設定などをユーザが直接で行わなければなりません。ま ったくの初心者には取付きにくいかもしれません。

このために「Cabinet」はメニュー処理のためのスクリプト「cab」が用意されてい ます。シェルについてまったくの初心者はメニューで慣れてから移った方が無難で しょう。

メニューの起動

メニュー処理用のスクリプト「cab」を走らせると「Cabinet」の操作メニューが表 示されます。起動した「cab」は親シェル(ほとんどの場合はログインシェルでし ょう)からコピーされてきた変数「CABINET」の内容で指定されたものを操作の対 象とします。もし、この時に親シェルが「CABINET」という変数を export してい なかったり、export していても中身が空の場合は「Book」を操作対象とします。 このメニュー画面で操作できる対象は

1. 親シェルが export した変数 CABINET で指定されたもの
2. Book
の順となります。メニュースクリプト「cab」が起動されると次のような画面が表 れます。
$ ./cab


Cabinet = Book  72 items
==================================================
Would you like to:

      1) Search data in the Cabinet
      2) Add data to the Cabinet
      3) List all of the Cabinet
      4) Change output (current: Display)
      5) Quit
==================================================
                                SELECT (1-5): 
一番上に表示されている「Book 72 items」とは現在開かれている(シェル変数 CABINET で指定されている)キャビネットの名称とその中にあるデータ件数です。 cab は、この Book を対象に検索、追加、表示の操作を行います。

現在開かれているキャビネットから指定されたキーワードを含む項目を表示します。 「1」を選びリターンキーを押すと探すためのキーワードを聞いてきます。ここで 目的のキーワードを指定します。指定できるキーワードには一般の語句はもちろん、 正規表現を用いることもできます。例えば「IBM PC」というタイトルのついた文献 を探したければ次のようにします。

[SEARCH] Enter Keyword: IBM PC
しばらくすると見つかった項目数とそれらの内容が表示されます。もし、表示する 内容が1画面に納まらない場合は more がポーズをかけますので、スペースで1画 面、リターンで1行先に進みます。

(2) データの追加

現在開かれているキャビネットに新しいデータを追加します。追加は事前にテキス トエディタなどで作っておいたファイルの内容を追加する方法と1項目だけ手作業 で入力する方法があります。どちらの場合も「2」を選択します。

すると、追加するデータを収めたファイル名を聞いてきます。データファイルを用 意しているならばそのファイル名を指定してください。このファイル名にはパスを 含むこともできますし、スペースで区切って複数のファイルを指定することもでき ます。

もし、指定したファイルが存在しない場合はエラーとなりデータの追加は行われま せん。追加するデータの内容についてはまったく検査されませんので、間違った指 定をすると文献データにビデオやCDのデータ等が混じってしまったり、文献デー タでもフィールドのならびが既存のものと一致ていなければ結果がおかしなものに なってしまいます。読み込むファイルの内容ついては十分に注意してください。 サンプルとして添付している文献データのレコード構造は「CAB_DATA データ形式」 で説明したものです。例えば

[ADD] Enter filename: b00 ../b002
と指定するとカレントディレクトリにある「b001」というファイルと「../b002」 というファイルの内容が文献データに追加されます。

もし、ここでファイル名を入力せずにリターンキーのみを押すと手作業で1件だけ データを追加できます。このときには著者、タイトル、出版社、年、ISBN の5項 目の入力を促してきます。入力が完了すると追加したいデータ内容を表示し、確認 を求めてきます。入力データに間違いがなければ「y」で答えてください。入力さ れたデータの確認が取れたならば追加されます。「y」以外のキーが押されたなら ば入力されたデータが捨てられます。手作業の入力は概ねつぎのようになります。

[ADD] Enter filename:
Type in NEW data
Auther -> Andrew S.Tanenbaum
Title -> OPERATING SYSTEM DESIGN AND IMPLEMENTATION
Publisher -> Prentice-Hall
Year -> 1988
ISBN -> 0-13-637331-3

----------------------------
Auther: Andrew S.Tanenbaum
Title: OPERATING SYSTEM DESIGN AND IMPLEMENTATION
Publisher: Prentice-Hall
Year: 1988
ISBN: 0-13-637331-3
----------------------------
                Ok? (y/n) 

(3) 全項目の表示

現在開かれているキャビネットに収められているすべてのデータを表示します。こ こでは「3」を選択するだけです。画面に表示するときは more が1画面ごとにポ ーズを入れてきます。「(4) Change output」で出力をプリンタ(lpr) に切り換 えているならばプリンタに連続出力されます。

(4) 出力の切り替え

検索されたデータと全項目を表示するときの出力先を切り換えます。「4」を選択 するたびにディスプレイとプリンタをスイッチし、現在の出力先は「cab」のメニ ュー画面に表示されています。

(5) 終了

メニューを終了し、シェルのコマンドラインに戻ります。

コマンドラインからの使用法

すでに説明したメニューからの操作は「cab」がそれぞれスクリプトを呼び出して 実現しています。それらをコマンドラインから呼び出して直接使うこともできます。 これらのスクリプトは実行されると操作対象とするキャビネットを決めるために必 ずシェル変数「CABINET」の内容を参照します。もし、この変数が export されて いなかったり内容が空の場合は正常な処理は期待できません。文献データを処理し たいのでしたら最初にログインシェルのプロンプトからシェル変数「CABINET」 「Book」を設定します。

$ CABINET=Book; export CABINET

(1)検索

検索をするスクリプトは「se (SEarch)」です。これに検索させたいキーワードを 引数として与えます。例えば「IBM PC」をキーワードとして与えたい場合には引数 をシェルから保護するために単一引用符で囲んで「se 'IBM PC'」というようにし ます。また、次のような正規表現を使ったり、検索結果をパイプに流すこともでき ます。

$ se '.*NIX' | more

(2)追加

「add」というスクリプトが担当します。追加したいデータが入っているファイル 名を引数として与えます。例えば「/tmp/mybooks」というファイルに追加したいデ ータが入っているとすると

$ add /tmp/mybooks
とします。引数として与えるファイル名は1つに限りませんが、指定したファイル すべてが見つからない場合はエラー中断しますのでデータの追加は行われません。

もし、引数を与えなかった場合は1件のみを手作業で入力するように動作します。 手作業入力の具体的な例は「メニューからの操作」部分を参照してください。

(3)全表示

スクリプト「all」を走らせると登録されているすべての項目を表示します。すべ ての項目をプリンタに送りたい場合は次のようにすれば良いでしょう。また、画面 で見たいのでしたら more や less などのページャの入力にパイプでつないでくだ さい。

$ all | lpr

(4)更新

人間が入力したデータからキャビネットを構成するスクリプト類が操作しやすい形 に変換し、検索用のデータを更新します。この仕事をする {\bf upd}は引数なしで 使います。なお、この「upd」スクリプトは最初に1度だけ使うもので、あとは 「add」でデータを追加する度、自動的に「upd」が呼び出されます。

$ upd

Cabinetの拡張への試み

「Cabinet」には不必要になったデータを削除するためのスクリプトがありません。 腕試しにチャレンジしてみてはいかがでしょうか。大まかには

1. 削除したい項目を入力させる(引数として指定する)
2. それに一致する項目を見つけ
3. オペレータの確認を得たのちに
4. 該当項目を削除する
5. できればその際に元のデータのバックアップを取る
という手順行えば良いと思います。そのためにはこの研修用のディスクにある限ら れたツールをどんなオプションで、どのよう組み合わせれば良いか? 等々、興味 は尽きません。

また、この Cabinet は文献ファイルをアクセスする時に排他制御も行なっていま せんのでこちらも試してみてください。

「Cabinet」の文献リスト・データ

4.3BSD UNIX Operating System
Samuel J.Leffer/Marshall K.McKustick/Micael J.Karels/John S.Quarterman
Addison-Wesley, 1989   ISBN 0-201-06196-1

ADVANCED UNIX PROGRAMMING
Mark J.Rochkind
Prentice-Hall, 1985   ISBN 0-13-011800-1
(UNIXシステムコール・プログラミング, ASCII,  ISBN 4-87148-360-X)

BCPL and C
Glyn Emery
Blackwell Science Publications, 1986   ISBN 0-632-01571-3

BCPL the language and its compiler
Martin Richards/Colin Whitby-Strevens
Cambridge University Press, 1980   ISBN 0-521-28681-6

BISON
Charles Donnelly and Richard Stallman
FSF, 1988

COMPILER DESIGN AND CONSTRUCTION Tools and Techniques With C and Pascal
Arthur B.Pyster
Van Nostrand Reinhold, 1988   ISBN 0-442-27536-6

COMPILER DESIGN IN C
Allen I.Holub
Prentice-Hall, 1990   ISBN 0-13-155045-4

Compilers Principles, Techniques, and Tools
Alfred V.Aho/Ravi Sethi/Jefffrey D.Ullman
Addison-Wesley, 1986   ISBN 0-201-10088-6

Computer Games I
David N.L.Levy ed.
Springer-Verlag, 1987   ISBN 0-387-96496-7

Computer Games II
David N.L.Levy ed.
Springer-Verlag, 1987   ISBN 0-387-96609-9

COMPUTER NETWORKS
Andrew S.Tanenbaum
Prentice-Hall, 1981   ISBN 0-13-165183-8

Data Structures and C Programs
Christopher J.Van Wyk
Addison-Wesley, 1988   ISBN 0-201-16116-8

DOCUMENT FORMATTING & TYPESETTING ON THE UNIX SYSTEM Vol.1
Narain Gehani
Silicon Press, 1988   ISBN 0-9615336-2-5

DOCUMENT FORMATTING & TYPESETTING ON THE UNIX SYSTEM Vol.2
Narain Gehani
Silicon Press, 1988   ISBN 0-9615336-3-3

EXPLORING THE UNIX SYSTEM
Stephen G.Kochan/Patrick H.Wood
Hayden Book Company, 1984   ISBN 0-8104-6268-0

GDB Manual
Richard M.Stallman
FSF, 1988

GNU Emacs Manual
Richard Stalllman
FSF, 1987

GNU Make
Richard M.Stallman/Roland McGrath
FSF, 1989

INTERNETWORKING WITH TCP/IP PRINCIPLES, PROTOCOLS, AND ARCHITECTURE
Douglas Comer
Prentice-Hall, 1988   ISBN 0-13-470188-7

INTRODUCTION TO COMPILER CONSTRUCTION WITH UNIX
Axel T.Schreiner/H.George Friedman Jr.
Prentice-Hall, 1985   ISBN 0-13-474396-2

LIFE WITH UNIX
Don libes/Sandy Ressler
Prentice-Hall,
(Life with UNIX, ASCII,  ISBN 4-7561-0783-4)

Managing UUCP and Usenet
Tim O'Reilly/Grace Todino
O'Reilly & Associates, Inc, 1989   ISBN 0-937175-09-9
(UUCPシステム管理, ASCII,  ISBN 4-7561-0087-2)

MINIX FOR THE IBM PC,XT,AND AT REFERENCA MANUAL
Andrew S.Tanenbaum
Prentice-Hall, 1988   ISBN 0-13-584400-2

Modern Operating Systems
Andrew S.Tanenbaum
Prentice-Hall, 1992   ISBN 0-13-595752-4

More Programming Pearls
John Bentley
Addison-Wesley, 1988   ISBN 0-201-11889-0

OPERATING SYSTEM DESIGN THE XINU APPROACH
Douglas Comer
Pretice-Hall, 1984   ISBN 0-13-637339-1

OPERATING SYSTEM DESIGN THE XINU APPROACH, MACINTOSH EDITION
Douglas Comer
Pretice-Hall, 1989   ISBN 0-13-638529-X

OPERATING SYSTEM DESIGN THE XINU APPROACH, P.C.EDITION
Douglas Comer
Pretice-Hall, 1988   ISBN 0-13-638180-4

OPERATING SYSTEM DESIGN-VOLUME II INTERNETWORKING WITH XINU
Douglas Comer
Prentice-Hall, 1987   ISBN 0-13-637646-0

OPERATING SYSTEMS DESIGN AND IMPLEMENTATION
Andrew S.Tanenbaum
Prentice-Hall, 1987   ISBN 0-13-637331-3

Practical C Programming
Qualine,S
O'reilly & Associates, 1991,   ISBN 0-937175-65-X

Practical UNIX Security
Simson Garfinkel/Gene Spefford
O'reilly & Associates, 1991,   ISBN 0-937175-72-2
(UNIXセキュリティ,ASCII,  ISBN 4-7561-0274-3)

PREPARING DOCUMENTS WITH UNIX
Constance C.Brown/Jack L.Falk/Richard D.Sperline
Prentice-Hall, 1986   ISBN 0-13-699976-X

PROGAMS AND DATA STRUCTURES IN C
Leendert Ammeraal
John Willey & Sons, 1987   ISBN 0-471-91751-6

Programming Pearls
John Bentley
Addison-Wesley, 1986   ISBN 0-201-10331-1

Programming the UNIX System
M.R.M.Dunsmuir/G.J.Davis
Macmillan, 1985   ISBN 0-333-37156-9

PROGRAMS AND DATA STRUCTURES IN C
Leendert Ammerael
John Weiley, 1987   ISBN 0-471-91751-6
(C-データ構造とプログラム, オーム社,  ISBN 4-274-07552-4)

Solutions in C
Rex Jaeschke
Addison-Wesley, 1986   ISBN 0-201-15042-5

STRUCTURED COMPUTER ORGANIZATION
Andrew S.Tanenbaum
Prentice-Hall, 1984   ISBN 0-13-854489-1

Termcap
Richard M.Stallman
FSF, 1988

Termcap & Terminfo
John Strang/Tim O'Reilly/Linda Mui
O'Reilly & Associates, 1989   ISBN 0-93717522-6

Texinfo
Richard M.Stallman/Robert J.Chassel
FSF, 1988

TEXT PROCESSING AND TYPESETTING WITH UNIX
David Barron/Mike Rees
Addison-Wesley, 1987   ISBN 0-201-14219-8

The AWK Programming Language
Alfred V.Aho/Brian W.Kernighan/Peter J.Weinberger
Addison-Wesley, 1988   ISBN 0-201-07981-X
(プログラミング言語AWK, トッパン,  ISBN 4-8101-8008-5)

THE BELL SYSTEM TECHNICAL JOURNAL
Bell System
JULY 1978, 1978

THE C PROGRAMMING LANGUAGE
Brian W.Kernighan/Dennis M.Ritchie
Prentice-Hall, 1978   ISBN 0-13-110163-3

The Design of the UNIX Operating System
Maurice.J.Bach
Prentice-Hall, 1987   ISBN 0-13-201799-7
(UNIXカーネルの設計(bit 別冊), 共立出版)

The GAWK Mannual
Diane Barlow Close/Arnold D.Robbins/Paul H.Rubin/Richard Stallman
FSF, 1989

The Kornshell Command and Programming Language
Morris I.Bolsky/David G.Korn
Prentice-Hall, 1989   ISBN 0-13-516972-0

THE UNIX OPERATING SYSTEM
Kaare Christian
Weily-Interscience, 1983   ISBN 0-471-87542-2

THE UNIX operating system BOOK
Mike Banahan/Andy Rutte
John Wiley & Sons, 1983   ISBN 0-471-89676-4

The UNIX Shell Programming Language
Rod Manis/Marc H.Meyer
Howard W.Sams, 1986   ISBN 0-672-22497-6

THE UNIX System
S.R.Bourne
Addison-Wesley, 1983   ISBN 0-201-13791-7
(UNIXシステム, マイクロソフトウェア)

THE UNIX SYSTEM V ENVIRONMENT
Stephen R.Bourne
Addison-Wesley, 1987   ISBN 0-201-18484-2

THE UNIX TEXT PROCESSING SYSTEM
Kaare Christian
John Wiley & Sons, 1987   ISBN 0-471-85581-2

troff Typesetting for UNIX Systems
Sandra L.Emerson/Karen paulsell
Prentice-Hall, 1987   ISBN 0-13-930959-4

UNIX FOR SUPER-USERS
Eric Foxley
Addison-Wesley, 1985   ISBN 0-201-14228-7

UNIX NROFF/TROFF A User's Guide
Kevin P.Roddy
Holt,Rinehart and winston, 1987   ISBN 0-03-000167-6

UNIX Papers for UNIX Developpers and Power Users
Mitchell Waite ed.
Howard W.Sams & Company, 1987   ISBN 0-672-22578-6

UNIX programmer's manual vol 1
Bell Lab.
Holt,Rinehart and Winston, 1983   ISBN 0-03-061742-1

UNIX programmer's manual vol 2
Bell Lab.
Holt,Rinehart and Winston, 1983   ISBN 0-03-061743-X

UNIX PROGRAMMING ON THE 80286 80386
Alan Deikman
M&T Publishing, Inc, 1989   ISBN 1-55851-060-5

UNIX PROGRAMMINIG ENVIRONMENT
Brian W.Kernighan/Rob Pike
Prentice-Hall, 1984   ISBN 0-13-937681-X
(UNIXプログラミング環境, ASCII,  ISBN 4-87148-351-7)

UNIX Syatem Readings and Applications Volume 2
AT&T Bell Laboratories
Prentice-Hall, 1987   ISBN 0-13-939845-7

UNIX SYSTEM ADMINISTRATION
David Fiedler/Bruce H.Hunter
Hayden Books, 1986   ISBN 0-8104-6289-3

UNIX SYSTEM PROGRAMMING
Keith Haviland Ben Salama
Addison-Wesley, 1987   ISBN 0-201-12919-1

UNIX System Software Readings
AT&T Unix Pacific Co.,Ltd
Prentice-Hall, 1988   ISBN 0-13-938358-1

UNIX Sytem Readings and Applications Volume 1
AT&T Bell Laboratories
Prentice-Hall, 1987   ISBN 0-13-938532-0

UNIX TEXT PROCESSING
Dale dougherty/Tim O'Reilly
HAYDEN BOOKS, 1987   ISBN 0-672-46291-5

Using UUCP and Usenet
Tim O'Reilly/Grace Todino
O'Reilly & Associates, Inc, 1987   ISBN 0-937175-10-2
(UUCP入門, ASCII,)

Writeing UNIX Device Drivers
George Pajari
Addison-Wesley, 1992   ISBN 0-201-523774-4

WRITING A UNIX DEVICE DRIVER
Janet I.Egan/Thomas J.Teixeita
John Wiley & Sons, 1988   ISBN 0-471-62811-5

参考文献

  • 平林浩一/平林小枝子,- UNIXのバックグラウンド,- プロセッサ No.64 Aug 1990 (技術評論社)
  • S.R. Bourne/三好・木下訳,- UNIX システム (マイクロソフトウェア)
  • Andrew S.Tanenbaum,- Minix 1.6.24B, shell ソース・リスト (Prentice Hall)