C Shell
C shell
C shell(シーシェル、csh)は、カリフォルニア大学バークレー校の大学院生だったビル・ジョイが1970年代後半に開発したUnixシェルである。1978年にジョイが配布を始めた 2BSD という BSD UNIX のリリースで広く配布されることになった。他にアイデアやコードに貢献した者としては、マイケル・ウベル、エリック・オールマン、マイク・オブライエン、ジム・カルプがいる。UNIX V6 の /bin/sh を元に作られたもので、Bourne shell (UNIX V7) と共通の先祖を持つ。
通常テキストウィンドウ内で動作するコマンドラインインタプリタであり、ユーザーがコマンドを入力するとそれに応じた処理が実行される。またシェルスクリプトと呼ばれるファイルからコマンド群を読み込むこともできる。他のUnixシェルと同様、ファイル名のワイルドカード、パイプ、ヒアドキュメント、コマンド置換、変数、条件分岐やループなどの制御構造をサポートしている。cshが1980年代の他のシェルと異なっていた点は、対話向けの機能と全体的なスタイルである。新機能によって他のシェルよりも容易に素早く使うことができた。言語としての全体的スタイルはC言語によく似ており、Unixユーザーにとっては読みやすかった。
macOS や Red Hat Linux など多くのシステムのcshは実際には改良版のtcshである。tcshの実行ファイルは "csh" と "tcsh" の両方にハードリンクされていて、どちらの名前でも同じ改良版のtcshが呼び出される。
DebianやUbuntuではcshとtcshの2種類のパッケージを用意しており、前者はオリジナルのBSD版csh、後者は改良版のtcshとなっている。両方をインストールした場合、cshとしてどちらを使用するかupdate-alternativesコマンドで選択できる。
tcshには、ファイル名やコマンドの補完機能、Tenexシステムに由来するコマンド行編集があり、名称の先頭の "t" は Tenex に因んでいる。tcshは機能を追加しただけでオリジナルのcshを修正したわけではないので、後方互換を保っていた。当初はジョイが作ったオリジナルのソースツリーからの脇枝だったが、今ではtcshが主な枝となっていて、開発が継続されている。tcshは非常に安定しているが、主に細かいバグ修正のため、およそ1年に1回の頻度で新たなリリースがなされている。
C shell の主たる設計目標は、C言語に似せることと、対話型利用での改良であった。
Unixシステムはほとんど全体がCで書かれているため、C shell の第一の目標はスタイル上システム全体と一貫性のあるコマンド言語とすることだった。キーワード、括弧の利用、組み込みの式の文法、配列サポートなどは全てCの影響を強く受けている。
今ではC言語によく似た文法のスクリプト言語がいくつもあり、それらに比べればcshはそれほどC言語に似ているとは言えない。しかし80年代から90年代にかけて、特にAT&Tでスティーブン・ボーンが開発したshと比べたときの違いは著しいと見られていた。次の例は、C shell の演算子や構文のわかりやすさを示したものである。
shには式の文法が存在しない。[(角括弧)で囲まれた条件式は、外部のtest (Unix)というプログラムで評価する必要がある。つまり、shのifコマンドは子プロセスを起動して引数を別のコマンドとして実行させる。その子プロセスが終了したときのリターンコードがゼロならthen節を探し(then節はifとは別の文だが、セミコロンをはさんで一行で書かれることが多い)、入れ子になったブロックを実行する。リターンコードがゼロ以外ならelse節を実行する。testプログラムを "test" と "[" の両方にハードリンクすることで、角括弧表記の利点が生まれ、testの機能があたかもshの一部であるかのような錯覚を与える。shで制御ブロックの終端にキーワードを逆に綴ったものを置くのは、ALGOL 68 のスタイルを踏襲したものである。
対照的にcshは自前で式を評価でき、高速である。可読性もよいと言われている。演算子や構文の多くはC言語のものをそのまま使っている。キーワードを逆に綴ることもなく、全体としてよりC言語に近いスタイルである。
次の例は、2の1乗から10乗までを計算するスクリプトを比較したものである。
やはりshには式の文法が存在しないため、shのスクリプトはコマンド置換とexprコマンドを使っている。C shell の @ 文(コマンド)は一種の駄洒落であり、"at-sign-ment" すなわち代入文を意味している。
最後の例は、switch文のスタイルの違いを示したものである。
shのスクリプトでは、";;" で各ケースの終りを示す。通常は空文を許さないため、これはケースの終りを目立たせるためである。
C shell の第二の設計目標は、対話型利用の改良だった。そのため、ユーザビリティや入力の高速性を追求したいくつかの新機能を導入している。必要な結果を得るのに打ち込まなければならないキーストローク数を減らすことで、高速性を実現している。特に重要なのは、ヒストリとその編集機構、エイリアス、ディレクトリスタック、チルダ記法、cdpath、ジョブコントロール、パスハッシングである。これら新機能は人気となり、多くが他のUnixシェルにも採用された。
素早いキーストロークで以前に入力したコマンド行を呼び出して再実行することができる。例えば、感嘆符を2つ "!!" と入力すると、直前に入力したコマンドを再実行できる。他にも "!$" と入力すると直前のコマンド行の最後の引数に置換される。
編集はヒストリ内のコマンドのテキストだけでなく、様々な置換が可能である。編集用作用素としては、単純な文字列検索/置換からファイルのパス名を構文解析して特定の部分を取り出すなどがある。
ユーザーが定義した何らかの文字列の別名(エイリアス)を設定でき、その別名を打ち込むと C shell がそれをユーザー定義文字列に置換する。例えば "fgrep" コマンドのエイリアスとして "f" を設定しておくとキーストロークが少なくなって高速化でき、スクリプトを作るよりも簡単である。
ディレクトリスタックは、カレントディレクトリをスタックにプッシュまたはポップでき、ファイルシステム内の複数個所で少ないキーストロークで行き来することができる。
ホームディレクトリを "~" で記述でき、ホームからの相対パスでファイルを指定できる。
Escキーを対話的に使用し、入力中のコマンド行の最後尾のファイル名を補完する可能性のある候補を示すことができる。
コマンド検索パス(環境変数のPATH)の記法で、cdコマンドを拡張するシェル変数。cdコマンドで指定されたディレクトリがカレントディレクトリにない場合、cdpathに指定されているディレクトリ群も調べる。
1980年代、多くのユーザーは単純なキャラクタ端末を使っていた。shの場合、一度に1つのことしかできなかった。ウィンドウを別に開くということができなかったため、ファイルの編集を開始するには、それまで行っていたことを終了させるなどする必要があった。C shell のジョブコントロールはこの問題を解決するもので、Ctrl-Z を押下することで現在実行中のジョブをサスペンドし、新たな C shell のインスタンスを生成することができる。そして、fg コマンドで複数のジョブを切り換えることができる。アクティブなジョブはフォアグラウンドジョブと呼ぶ。それ以外のジョブはサスペンド状態かまたはバックグラウンド状態となる。
パスハッシングとは、実行可能ファイルの検索を高速化する機能である。PATHに示されたディレクトリを順に見ていくのではなく、C shell 内部に構築したハッシュテーブルから実行可能ファイルを探す。"rehash" コマンドはそのハッシュテーブルをリフレッシュするもので、新たに実行可能ファイルを作成した場合などに使用する。
C shell は行単位で操作する。各行を字句解析して空白、括弧、パイプやリダイレクトを表す記号、セミコロン、アンパサンド等で区切られた単語の並びとして認識する。
基本の文は単にコマンドを実行するものである。先頭の単語がコマンド名として認識され実行される。"echo" などの内部コマンドの場合と外部コマンドの場合がある。それに続く単語列は、そのコマンドの引数として渡される。
基本構文レベルでは、以下のような文法の機能が存在する。
他のUnixシェルと同様、任意のコマンド行引数にワイルドカードを使用できる。ワイルドカード文字を含む単語がある場合、それをパターンとし、マッチするファイル名の一覧と置換する。
* は、任意長の文字列とマッチする。
? は、任意の1つの文字とマッチする。
[...] は、角括弧内の任意の文字とマッチする。ハイフンで範囲指定することもできる。
[!...] は、角括弧内の文字以外の任意の文字とマッチする。
cshではいくつか便利な記法を導入しており、他のUnixシェルにも採用されている。
abc{def,ghi} は、abcdef または abcghi に展開される。
~ は、カレントユーザーのホームディレクトリを意味する。
~user は、その user のホームディレクトリを意味する。
複数ディレクトリレベルのワイルドカード、例えば "*/*.c" といった記述も可能である。
ワイルドカード処理をシェルが行うようにしたことは、Unixにおける重要な決定の1つである。つまり、どのコマンドでも同じようにワイルドカードが使え、シェルだけがワイルドカード処理に必要なコードを備えていればよい。しかし、そのために子プロセス生成時のexecシステムコールには非常に長い引数を効率的に渡す必要が生じた。対照的にWindowsはコマンド行をUnicodeでおよそ32K文字までに制限しており、ワイルドカード処理は各アプリケーションが行うようになっている(実際にはC言語のmain()関数を実行する前にCのランタイムコードが自動的に行う)。これはMS-DOSからの伝統である。MS-DOSではアプリケーションに渡せるコマンド行は128バイトに制限されていたため、ワイルドカード処理をコマンドプロンプト側で行うのは非現実的だった。
cshでコマンドを実行する場合、デフォルトではcshの標準入力/標準出力/標準エラー出力をそのまま継承し、それらはcshが動作している端末(または端末エミュレータ)を指しているのが普通である。入出力リダイレクトを行うことで入力または出力に端末ではなくファイルを使うよう設定できる。
> file は、標準出力が file に書かれることを意味する。既存ファイルの場合は上書きし、無ければ新規作成する。エラーはシェルのウィンドウに表示される。
>& file は、標準出力と標準エラー出力の両方が file に書かれることを意味する。既存ファイルの場合は上書きし、無ければ新規作成する。
>> file は、標準出力が file の最後尾に追記されることを意味する。
>>& file は、標準出力と標準エラー出力の両方が file の最後尾に追記されることを意味する。
< file は、file から標準入力に読み込むことを意味する。
<< string は、ヒアドキュメントである。string にマッチする行が入力されるまでの入力内容を標準入力として読み込む。
コマンドは、次のような手段で1行に複数個連結することができる。
; は、1つめのコマンドを実行し、次に2つめのコマンドを実行することを意味する。
&& は、1つめのコマンドを実行し、そのリターンコードが0(成功)の場合、2つめのコマンドを実行する。
|| は、1つめのコマンドを実行し、リターンコードが0以外(失敗)の場合に2つめのコマンドを実行する。
複数のコマンドをパイプで接続でき、あるコマンドの出力を次のコマンドの入力とすることができる。この場合、2つのコマンドは並行して動作する。
| は、前のコマンドの標準出力を次のコマンドの標準入力に接続する。エラーはシェルのウィンドウに表示される。
|& は、前のコマンドの標準出力と標準エラー出力を次のコマンドの標準入力に接続する。
単語にドル記号 "$" がある場合、それに続く文字列を変数名と解釈し、その変数の値で置換する。変数にパス名を入れておくと、ヒストリの編集機構を使って特定部分(ファイル拡張子やファイル名本体のみなど)を取り出すこともできる。
引用機構は、空白、ワイルドカード、括弧、ドル記号など通常なら特殊な意味を持つ文字をリテラルテキストとして扱えるようにする。
\ は、続く文字を通常のリテラル文字として扱う。
"string" は弱い引用である。空白やワイルドカードはリテラルとして扱われるが、変数やコマンド置換はそのまま機能する。
'string' は強い引用である。囲まれた文字列全体がリテラルとして扱われる。
コマンド置換は、あるコマンドの出力を別のコマンドの引数として使えるようにする。
`command` は command を実行し、その出力でコマンド行の当該部分を置換する。
通常、コマンドを実行開始するとそれが終わるのを待ち合わせ、次のコマンドを実行するか、ユーザーのコマンド入力を促すプロンプトを表示する。
command & は、command をバックグラウンドで実行開始し、即座に次のコマンドを受け付けられるようにする。
サブシェルはシェルの子プロセスであり、現在の状態を継承しているが、それを変更することもできる。例えば、カレントディレクトリを変更しても親のカレントディレクトリは変化しない。
( commands ) は、commands をサブシェルで実行することを意味する。
csh は条件分岐と反復という制御構造を提供している。条件分岐としては if 文と switch文がある。反復としては、while 文、foreach 文、repeat 文がある。
if 文には2つの形式がある。短い形式は1行で済むが、式が真の場合に実行できるコマンドはひとつだけである。
長い形式は then、else、endif というキーワードを使いコマンドの並んだブロックを形成でき、その中でさらに条件分岐を入れ子にすることもできる。
else と if が同じ行に出現する場合、csh はそれを入れ子というよりも連鎖として扱う。つまり endif はひとつでよい。
switch 文は文字列をパターンの一覧と比較する。パターンにはワイルドカード文字を含んでもよい。どれもマッチしない場合 default アクションを実行し、マッチすればその部分を実行する。
while 文は式を評価する。その結果が真なら続くコマンド群を実行し、再び式の評価に戻る。
foreach 文は値の一覧(通常はワイルドカードで生成されたファイル名一覧)をとり、それぞれの値について値をループ変数に設定し、続くコマンド群を実行する。
repeat文は「整数値」で指定された回数だけ「コマンド」(ひとつのコマンド)を繰り返し実行する。
csh はシェル変数と環境変数を実装している。環境変数は setenv 文で生成でき、その値は常に単純な文字列であり、exec システムコール経由任意の子プロセスに引き継がれる。
シェル変数は set 文や @ 文で生成され csh 内部で使われる。子プロセスには渡されない。シェル変数は単純な文字列の場合と文字列の配列の場合がある。事前定義されたシェル変数もいくつかあり、csh 内部の各種オプションの制御に使われる。例えば、ワイルドカードが何にもマッチしなかった際の動作などを設定できる。
現在のバージョンの csh では、変数に格納できる文字列の長さは任意であり、数百万文字でもよい。
C shell はC言語の演算子を流用した文法で32ビット整数の式を評価する機能を実装している。他に文字列比較の演算子やファイルシステムのテスト演算子(あるファイルが存在するかどうかのテスト)もある。演算子とオペランドは空白で区切らなければならない。変数は $name の形式で参照する。
演算子の優先順位もC言語を踏襲しているが、優先順位の等しい演算子が並んでいるときの演算順序の曖昧さを解決する演算子の結合性はC言語とは異なる。C言語では多くの演算子で左から右へ結合していくのに対し、C shell では右から左に結合していく。以下に例を示す。
C shell での括弧はビットシフト演算子と入出力リダイレクトを混同しないために使用している。どちらの言語でも括弧を使えば評価順序を明確化できる。なお先述した通り、シェル変数の値は文字列であり、@ 文などの式の中でだけ文字列を数値に変換して評価し、結果を文字列に変換して変数に格納している。
いくつもの革新的機能により対話型利用では人気となったが、csh はスクリプト言語としては人気を獲得することはなかった。当初から1980年代末まで、cshはあらゆるUnixシステムに実装されていたわけではなく、shならばあらゆるUnixシステムに存在することが確実だった。したがって、様々なシステムで動作する可能性のあるスクリプトはshで書くのが賢明だった。1990年代中ごろにはcshも広く利用可能となったが、POSIXの委員会からcshをスクリプト言語として使用することに対して批判の声が挙がった。すなわち、対話用とスクリプト用の推奨シェルは1つであるべきだとし、POSIXとしては KornShell を推奨するとしたのである。C shell は他にも、文法上の欠陥、機能不足、実装のまずさといった点で批判された。
言語定義上、不必要な矛盾が生じている。例えば、set、setenv、alias というコマンドは、ある名前と文字列または単語の並びを結びつけるという基本的に同じ機能を有している。しかし、それらには全く不必要な若干の差異がある。set では等号を必要とするが、setenv や alias では等号は使わない。set では単語の並びを括弧で囲む必要があるが、setenv と alias ではそうではない。同様に、if の最後は endif、switch の最後は endsw、ループ系構文では最後が end というように意味も無く一貫性がない文法になっている。
よく言われるのは、標準入力ファイルハンドルの操作機能と関数サポートの欠如である。Bourne shell は局所変数は使えないが関数は定義できるのに対し、csh で関数に相当する機能はエイリアスしかなく、1行のコードしか定義できず、しかも制御構文の多くは途中に改行を必要とする。結果としてスタイルは似ていてもC言語のプログラムの機能をそのまま C shell で実装するのは困難である。そのため、大きなプロジェクトほどC言語や Bourne shell のスクリプトを使う傾向がある。
構文解析は場当たり的であり、多くの批判を浴びている。1970年代初めにはコンパイラ技術はそれなりに成熟しており、多くの言語はトップダウンまたはボトムアップ構文解析器を使って完全に再帰的な文法を認識できるように実装されていた。C shell で場当たり的な設計となった理由は不明である。ジョイは2009年のインタビューで「Unixに関して作業を始めたとき、私は優秀なプログラマではなかった」と述べており、単にそれが答えかもしれない。しかし、場当たり的な設計を選択したせいで C shell は完全再帰的ではなくなった。したがって、実現できる処理の複雑さには限度がある。
対話的にコマンドを入力して実行するぶんには快適だが、複雑なコマンドを実行させようとスクリプトを書いてみると時間がかかり、しかもよく失敗し、暗号のようなエラーメッセージを表示するか、好ましくない結果を生じることになる。例えば、C shell では制御構造間のパイプは不可能である。例えば foreach の出力をパイプで grep コマンドに送り込もうとしても単に機能しない。ワークアラウンドとしては、foreach を使った部分を別のスクリプトにして構文解析の問題を回避するという手段がある。こうすれば、そのスクリプトは別のcshのプロセスとして動作するのでパイプで接続することも自由である。
別の好ましくない動作の例としてコード断片を示す。下記のスクリプトはどちらも「'myfile' が存在しないなら、'mytext' をそこに書き込む形で生成せよ」という意味である。しかし、右側の例では常に空ファイルが生成される。何故なら C shell の評価順序はコマンド行単位であり、まず入出力リダイレクトを評価することになっているためで、myfile がその際に作られてしまい、ファイルの存在を調べたときには既に存在しているためである。
また、エラーメッセージが貧弱だという点もよく批判されている。例えば "0 event not found" というメッセージからは何が問題なのかもわからない。
ヒストリ機構、エイリアス、チルダ記法、対話的ファイル名補完、シェル内での式評価などといった機能は大きな成功だったと言え、他のUnixシェルでも採用された。しかしkshやbashなど多数の独立したクローンが生まれたshとは対照的に、cshのクローンとして知られているものは2つしかない(tcshはcshと独立して開発されたわけではなく、クローンとは言えない。tcshはビル・ジョイの書いたオリジナルのコードをベースとして機能を追加しただけである)。
アレン・ホーラブの1986年の著書 On Command: Writing a Unix-Like Shell for MS-DOS では、"SH" という名前のプログラムを解説しているが、これはshではなくcshの機能と言語設計をコピーしたものである。関連するフロッピーディスクにはSHと基本的なUnix風コマンド(cat、cp、grep など)のソースコードが格納されていて、それぞれ25ドルと30ドルで販売されていた。ホーラブのSHの制御構造、式の文法、ヒストリ機構などは全て C shell と同一だった。
1988年、Hamilton Laboratories がOS/2向けに Hamilton C shell を発売した。1992年には Windows NT 版をリリース。Windows版はその後も活発にサポートされているが、OS/2版は2003年でサポート終了している。Hamilton C shell は Nicole Hamilton が書いたもので、cshクローンとUnixユーティリティ群を含んでいる。初期のクイックリファレンスによれば、「(ジョブコントロールを除く)C shell 言語全体に完全準拠」としているが、言語仕様には若干の改良が見られ、UnixとPCの差異にも対応している。最大の改良点はトップダウン構文解析を採用した点で、制御構造の入れ子やパイプ連結が可能となっている。また、プロシージャを定義でき、ブロック構造に局所変数を定義でき、浮動小数点演算もサポートしている。PC向けの改良点としては、ファイル名などのPCにおける慣習に従っており、スレッドの生成で済む部分は子プロセスを生成するのではなくスレッドで対応している。
音声認識研究の分野では C shell がスクリプト言語としてよく使われている。これはHTKというツールがcshスクリプトを使っていることと関連している。