C Shell
作者 | ビル・ジョイ |
---|---|
初版 | 1978年 |
最新版 |
tcsh 6.18.00 / 2012年1月14日[1] |
リポジトリ | |
プログラミング 言語 | C |
対応OS | BSD, UNIX, Linux, macOS |
種別 | Unixシェル |
ライセンス | BSDライセンス |
設計目標と機能
編集C shell の主たる設計目標は、C言語に似せることと、対話型利用での改良であった。
C言語風のスタイル
編集Bourne shell | C shell |
---|---|
#!/bin/sh
if [ $days -gt 365 ]
then
echo This is over a year.
fi
|
#!/bin/csh
if ( $days > 365 ) then
echo This is over a year.
endif
|
[
︵角括弧︶で囲まれた条件式は、外部のtestというプログラムで評価する必要がある。つまり、shのifコマンドは子プロセスを起動して引数を別のコマンドとして実行させる。その子プロセスが終了したときのリターンコードがゼロならthen節を探し︵then節はifとは別の文だが、セミコロンをはさんで一行で書かれることが多い︶、入れ子になったブロックを実行する。リターンコードがゼロ以外ならelse節を実行する。testプログラムを "test
" と "[
" の両方にハードリンクすることで、角括弧表記の利点が生まれ、testの機能があたかもshの一部であるかのような錯覚を与える。shで制御ブロックの終端にキーワードを逆に綴ったものを置くのは、ALGOL 68 のスタイルを踏襲したものである[12]。
対照的にcshは自前で式を評価でき、高速である。可読性もよいと言われている。演算子や構文の多くはC言語のものをそのまま使っている。キーワードを逆に綴ることもなく、全体としてよりC言語に近いスタイルである。
次の例は、2の1乗から10乗までを計算するスクリプトを比較したものである。
Bourne shell | C shell |
---|---|
#!/bin/sh
i=2
j=1
while [ $j -le 10 ]
do
echo '2 **' $j = $i
i=`expr $i '*' 2`
j=`expr $j + 1`
done
|
#!/bin/csh
set i = 2
set j = 1
while ( $j <= 10 )
echo '2 **' $j = $i
@ i *= 2
@ j++
end
|
Bourne shell | C shell |
---|---|
#!/bin/sh
for i in d*
do
case $i in
d?) echo $i is short ;;
*) echo $i is long ;;
esac
done
|
#!/bin/csh
foreach i ( d* )
switch ( $i )
case d?:
echo $i is short
breaksw
default:
echo $i is long
endsw
end
|
shのスクリプトでは、";;
" で各ケースの終りを示す。通常は空文を許さないため、これはケースの終りを目立たせるためである。
対話型利用のための改良点
編集!!
" と入力すると、直前に入力したコマンドを再実行できる。他にも "!$
" と入力すると直前のコマンド行の最後の引数に置換される。
編集機構
編集はヒストリ内のコマンドのテキストだけでなく、様々な置換が可能である。編集用作用素としては、単純な文字列検索/置換からファイルのパス名を構文解析して特定の部分を取り出すなどがある。
エイリアス
ユーザーが定義した何らかの文字列の別名︵エイリアス︶を設定でき、その別名を打ち込むと C shell がそれをユーザー定義文字列に置換する。例えば "fgrep
" コマンドのエイリアスとして "f
" を設定しておくとキーストロークが少なくなって高速化でき、スクリプトを作るよりも簡単である。
ディレクトリスタック
ディレクトリスタックは、カレントディレクトリをスタックにプッシュまたはポップでき、ファイルシステム内の複数個所で少ないキーストロークで行き来することができる。
チルダ記法
ホームディレクトリを "~
" で記述でき、ホームからの相対パスでファイルを指定できる。
対話的ファイル名補完
Escキーを対話的に使用し、入力中のコマンド行の最後尾のファイル名を補完する可能性のある候補を示すことができる。
cdpath
コマンド検索パス︵環境変数のPATH︶の記法で、cdコマンドを拡張するシェル変数。cdコマンドで指定されたディレクトリがカレントディレクトリにない場合、cdpathに指定されているディレクトリ群も調べる。
ジョブコントロール
1980年代、多くのユーザーは単純なキャラクタ端末を使っていた。shの場合、一度に1つのことしかできなかった。ウィンドウを別に開くということができなかったため、ファイルの編集を開始するには、それまで行っていたことを終了させるなどする必要があった。C shell のジョブコントロールはこの問題を解決するもので、Ctrl-Z を押下することで現在実行中のジョブをサスペンドし、新たな C shell のインスタンスを生成することができる。そして、fg
コマンドで複数のジョブを切り換えることができる。アクティブなジョブはフォアグラウンドジョブと呼ぶ。それ以外のジョブはサスペンド状態かまたはバックグラウンド状態となる。
パスハッシング
パスハッシングとは、実行可能ファイルの検索を高速化する機能である。PATHに示されたディレクトリを順に見ていくのではなく、C shell 内部に構築したハッシュテーブルから実行可能ファイルを探す。"rehash" コマンドはそのハッシュテーブルをリフレッシュするもので、新たに実行可能ファイルを作成した場合などに使用する。
スクリプト言語としての C Shell
編集C shell は行単位で操作する。各行を字句解析して空白、括弧、パイプやリダイレクトを表す記号、セミコロン、アンパサンド等で区切られた単語の並びとして認識する。
基本構文
編集echo
" などの内部コマンドの場合と外部コマンドの場合がある。それに続く単語列は、そのコマンドの引数として渡される。
基本構文レベルでは、以下のような文法の機能が存在する。
*
は、任意長の文字列とマッチする。
?
は、任意の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 をサブシェルで実行することを意味する。
制御構造
編集if
文
編集
if
文には2つの形式がある。短い形式は1行で済むが、式が真の場合に実行できるコマンドはひとつだけである。
if (式) コマンド
長い形式は then
、else
、endif
というキーワードを使いコマンドの並んだブロックを形成でき、その中でさらに条件分岐を入れ子にすることもできる。
if (式1) then
コマンド11
コマンド12
コマンド13
...
else if (式2) then
コマンド21
コマンド22
コマンド23
...
else
コマンドn1
コマンドn2
コマンドn3
...
endif
else
と if
が同じ行に出現する場合、csh はそれを入れ子というよりも連鎖として扱う。つまり endif
はひとつでよい。
switch
文
編集
switch
文は文字列をパターンの一覧と比較する。パターンにはワイルドカード文字を含んでもよい。どれもマッチしない場合 default
アクションを実行し、マッチすればその部分を実行する。
switch (文字列)
case パターン1:
コマンド列11
コマンド列12
コマンド列13
:
breaksw
case パターン2:
コマンド列21
コマンド列22
コマンド列23
:
breaksw
:
default:
コマンド列n1
コマンド列n2
コマンド列n3
:
endsw
while
文
編集
while
文は式を評価する。その結果が真なら続くコマンド群を実行し、再び式の評価に戻る。
while (式)
コマンド1
コマンド2
コマンド3
...
end
foreach
文
編集
foreach
文は値の一覧(通常はワイルドカードで生成されたファイル名一覧)をとり、それぞれの値について値をループ変数に設定し、続くコマンド群を実行する。
foreach ループ変数 (値1 値2 値3 ... 値n)
コマンド1
コマンド2
コマンド3
...
end
repeat文
編集repeat文は「整数値」で指定された回数だけ「コマンド」(ひとつのコマンド)を繰り返し実行する。
repeat 整数値 コマンド
変数
編集exec
システムコール経由任意の子プロセスに引き継がれる。
シェル変数は set
文や @
文で生成され csh 内部で使われる。子プロセスには渡されない。シェル変数は単純な文字列の場合と文字列の配列の場合がある。事前定義されたシェル変数もいくつかあり、csh 内部の各種オプションの制御に使われる。例えば、ワイルドカードが何にもマッチしなかった際の動作などを設定できる。
現在のバージョンの csh では、変数に格納できる文字列の長さは任意であり、数百万文字でもよい。
式
編集$
name の形式で参照する。
演算子の優先順位もC言語を踏襲しているが、優先順位の等しい演算子が並んでいるときの演算順序の曖昧さを解決する演算子の結合性はC言語とは異なる。C言語では多くの演算子で左から右へ結合していくのに対し、C shell では右から左に結合していく。以下に例を示す。
// C groups from the left
// prints 4
int i = 10 / 5 * 2;
printf( "%d\n", i );
// prints 5
i = 7 - 4 + 2;
printf( "%d\n", i );
// prints 16
i = 2 >> 1 << 4;
printf( "%d\n", i );
|
# C shell groups from the right
# prints 1
@ i = 10 / 5 * 2
echo $i
# prints 1
@ i = 7 - 4 + 2
echo $i
# prints 0
@ i = ( 2 >> 1 << 4 )
echo $i
|
C shell での括弧はビットシフト演算子と入出力リダイレクトを混同しないために使用している。どちらの言語でも括弧を使えば評価順序を明確化できる。なお先述した通り、シェル変数の値は文字列であり、@ 文などの式の中でだけ文字列を数値に変換して評価し、結果を文字列に変換して変数に格納している。
批判
編集set
、sete
nv
、alias
というコマンドは、ある名前と文字列または単語の並びを結びつけるという基本的に同じ機能を有している。しかし、それらには全く不必要な若干の差異がある。set
では等号を必要とするが、setenv
や alias
では等号は使わない。s
et
では単語の並びを括弧で囲む必要があるが、setenv
と alias
ではそうではない。同様に、if
の最後は endi
f
、switch
の最後は endsw
、ループ系構文では最後が end
というように意味も無く一貫性がない文法になっている。
機能不足
よく言われるのは、標準入力ファイルハンドルの操作機能と関数サポートの欠如である。Bourne shell は局所変数は使えないが関数は定義できるのに対し、csh で関数に相当する機能はエイリアスしかなく、1行のコードしか定義できず、しかも制御構文の多くは途中に改行を必要とする。結果としてスタイルは似ていてもC言語のプログラムの機能をそのまま C shell で実装するのは困難である。そのため、大きなプロジェクトほどC言語や Bourne shell のスクリプトを使う傾向がある。
実装のまずさ
構文解析は場当たり的であり、多くの批判を浴びている。1970年代初めにはコンパイラ技術はそれなりに成熟しており[16]、多くの言語はトップダウンまたはボトムアップ構文解析器を使って完全に再帰的な文法を認識できるように実装されていた。C shell で場当たり的な設計となった理由は不明である。ジョイは2009年のインタビューで﹁Unixに関して作業を始めたとき、私は優秀なプログラマではなかった﹂と述べており、単にそれが答えかもしれない[17]。しかし、場当たり的な設計を選択したせいで C shell は完全再帰的ではなくなった。したがって、実現できる処理の複雑さには限度がある。
対話的にコマンドを入力して実行するぶんには快適だが、複雑なコマンドを実行させようとスクリプトを書いてみると時間がかかり、しかもよく失敗し、暗号のようなエラーメッセージを表示するか、好ましくない結果を生じることになる。例えば、C shell では制御構造間のパイプは不可能である。例えば foreach
の出力をパイプで
grep
コマンドに送り込もうとしても単に機能しない。ワークアラウンドとしては、foreach
を使った部分を別のスクリプトにして構文解析の問題を回避するという手段がある。こうすれば、そのスクリプトは別のcshのプロセスとして動作するのでパイプで接続することも自由である。
別の好ましくない動作の例としてコード断片を示す。下記のスクリプトはどちらも﹁'myfile' が存在しないなら、'mytext' をそこに書き込む形で生成せよ﹂という意味である。しかし、右側の例では常に空ファイルが生成される。何故なら C shell の評価順序はコマンド行単位であり、まず入出力リダイレクトを評価することになっているためで、myfile がその際に作られてしまい、ファイルの存在を調べたときには既に存在しているためである。
# Works as expected
if ( ! -e myfile ) then
echo mytext > myfile
endif
|
# Always creates an empty file
if ( ! -e myfile ) echo mytext > myfile
|
また、エラーメッセージが貧弱だという点もよく批判されている。例えば "0 event not found" というメッセージからは何が問題なのかもわからない。
影響
編集脚注
編集参考文献
編集関連項目
編集外部リンク
編集- 有害な csh プログラミング
- Cシェルプログラミング
- An Introduction to the C shell by William Joy.
- Linux in a Nutshell: Chapter 8. csh and tcsh.
- tcsh home page.
- historical 2BSD csh source code dated February 2, 1980.
- The Unix Tree, complete historical Unix distributions.