* csh/tcsh *

◎ はじめに

・ シェル

シェルはユーザーが OS の機能を使うためのインターフェースです。

現在、bash、csh、tcsh、wish、zsh など、多くの種類のシェルが存在しており、それぞれ特有の文法や機能を持っています。

・ シェルスクリプト

ユーザーは普段、一行ずつコマンドライン入力を行うことでシェルを利用します。(GUIなシェルは除く)

しかし、決まりきった一連の処理を繰り返し行う場合など、いちいちコマンド入力を行うのは大変手間がかかります。

そこで、一連の処理をファイルにまとめたシェルスクリプトが力を発揮します。

変数、関数、条件分岐、ループといったシェルの機能を用いて構造的なプログラムを記述することができます。

・ 注意

tcsh は csh を拡張、バグ修正がなされた版です。

ここで紹介している構文はほとんど tcsh を前提にして記述してあるので、もしかしたら csh ではうまく動かないものもあるかもしれません。

また、シェルにはかなりの数のコマンドが用意されていますが、ここでは一部の基本的なコマンド、しかもそのコマンドのさらに一部の構文しか紹介していません。

ある程度慣れて、詳細を知る必要が生じたなら公式のドキュメントを見ることをお勧めします。

最後に、なぜ csh でプログラムを書くのが良くないのかという記事に目を通しておくのも良いと思います。

◎ スクリプトの書き方

スクリプト自体は単なるテキストファイルです。

使い慣れたエディタを使って気軽に記述していきましょう。

ほとんど全てのスクリプトに共通する基本的な事項を次にまとめておきます。

・ #! 行

スクリプトの先頭には #! 行を記述しておくのが一般的です。

#! 行には、スクリプトを実行するプログラムのフルパス(及び引数)を指定します。

例えば、スクリプトが tcsh 用であれば

#!/bin/tcsh

スクリプトが bash 用であれば

#!/bin/bash

という具合に、スクリプトの先頭行に記述します。

perl の CGI スクリプトを見たことのある方は次のような記述を見たことがあると思います。

#!/usr/bin/perl

これはスクリプトを /usr/bin/perl に引き渡すという意味なのです。

もし、シェルプログラムがどこにあるのかがわからないような場合は、whereis や which、find などで探してみてください。

現在実行している行を逐一表示させたいときは、-x か -v を引数として渡します。( これらは csh 自身のコマンドラインオプションです。)

#!/bin/tcsh -x

つづいてコマンドラインから chmod でファイルに実行属性をつけてあげましょう。

% chmod +x script.sh

これで、次のようにしてスクリプトを実行できるようになります。

% ./script.sh

現在のシェル環境が #! 行を参照して、適切なシェルプログラムにスクリプトを引き渡してくれます。

もし、#! 行を書かなかったり、実行属性を付加しないような場合は、次のように直接シェルプログラムを指定して実行します。

% tcsh ./script.sh

・ コメント

ほとんどのスクリプトでは # 以降がコメントであるとみなされます。

次のスクリプトを見てください。

#!/bin/sh
echo hello
#echo good bye
echo hello #終わり

このスクリプトを実行すると、hello を2回表示して終了します。

行の途中に # がある場合も、それ以降がコメントとみなされます。(ただし、# が backslash でエスケープされている場合などは例外です。)

もちろん、先頭の #! 行もスクリプト自身にとってみればコメント扱いです。

◎ 変数の扱い方

・ ワンポイント

変数の扱い方についてですが、一般的なプログラミング言語とはなり、少々クセがあります。

慣れないうちは次のように覚えておくと間違えにくいと思います。

$ は後続するシンボルの値を展開する役目を持っています。

いろいろな $ の使い方があるのですが、初めは上記のようにシンプルに覚えておくと良いと思います。

例えば、後で説明する set、@、foreach といったコマンドでは変数への値の代入が生じます。

このとき、変数名に $ をつけないよう気をつけてください。

・ 変数の展開

変数 var が値 "hello" を持っているとき、

$var
${var}

上記のどちらも同じ意味であり、 "hello" に展開されます。

しかし、変数につづいて文字列を後付けする次のような例では、

$varween
${var}ween

前者は変数 varween を参照しようとしてしまいます。

これは期待外の挙動であるばかりか、変数 varween が存在していなければエラーが起きます。

後者では変数 var と後続する文字列 ween の境界が明確であり、"helloween" という文字列を得ることができます。

$ を使うか ${} を使うかはユーザーの好みによりますが、誤った処理が行なわれないように配慮して使いわけて下さい。

◎ 変数への値の代入

・ set

シェル変数に値を代入するには set を使います。

set コマンドの基本的な構文は次の通りです。

set 変数名 = 値 ...

引数無しで set を使用するとシェル変数一覧を表示することができます。

ちなみに変数名と値の間に空白を挟まない記述も有効ですが、視認性は悪くなるでしょう。

では、以下のようなスクリプトを作成し、実行してみましょう。

#!/bin/tcsh
set val1 = 10
set val2 = hello
set val3 = $val2
set val4 = val2
set val5 = "hello world"
set

val1 には 10、 val2 には hello、 val3 には hello、 val4 には val2、 val5 には hello world という文字列が代入されます。

さらに、最終行の引数なしの set によって変数一覧が表示されます。

では、val1 の代入から見ていきましょう。

set によって、val1 には 「文字列」 "10" が代入されます。

一見、「数値」 10 が代入されているように思われるかもしれませんが、シェル変数が保持する値は全て文字列です。

数値演算コマンドなどを実行したときに数値として扱われるだけで、実際は常に文字列です。

set x = 1
set y = 2
set z = ${x}+${y}

というような記述をしても、変数 z には "1+2" という文字列が代入されるだけなのです。

変数を数値として扱うコマンド群については後述します。

次に val2 の代入を見てみましょう。

これは val1 と同様に、文字列 "hello" を代入しています。

val3 には変数 val2 を展開した値が代入されます。

val2 の値を参照するので val2 の先頭に $ が付いていることに気をつけて下さい。

val4 の代入はわざと間違った記述をしてみました。

右辺の val2 に $ が付いていません。

そのため、val4 には 文字列 "val2" が代入されます。

val5 には、文字列 "hello world" が代入されます。

シェルコマンドにおいては、空白文字が区切り記号として扱われます。

したがって

set val5 = hello world

というような記述では、val5 に文字列 "hello" が代入され、値が空である変数 world が定義されてしまいます。

これは set が一度に複数の変数を定義することを認めているからです。(set の構文を参照してください)

文字列が空白を含むだけの場合に限らず、特殊な文字を含んでいたりする場合にもクォートを用います。

主観ではありますが、基本的に文字列は全てクォートでくくっておいたほうが良いと思います。

クォートについては後述します。

・ unset

変数を未定義の状態にもどすコマンドは unsetです。

構文は以下の通りです。

unset 変数名

変数の値を参照するわけではないので、指定する変数名の先頭に $ を付けないように気をつけてください。

・ setenv と unsetenv

set や unset が扱う変数はシェル変数のみです。

環境変数の設定を行なう場合は setenv、 unsetenv コマンドを使います。

構文はそれぞれ次のとおりです。

setenv 変数名 値
unsetenv 変数名

setenv は set とちがって = を使わない点に気をつけましょう。

◎ 出力する

文字列や変数の値を標準出力に出力するには echo コマンドを使います。

echo Hello!
echo "Good Bye!"

この例では Hello! と表示した後、Good Bye! と表示します。

◎ クォート展開について

先の例では文字列をダブルクォーテーションでくくっていましたが、csh には数種類のクォートが用意されています。

状況によって、これらを使いわけましょう。

・ ダブルクォーテーション

set name1 = "John"
set name2 = "Jack"
set condition = "ok"
echo "${name1} is ${condition}.\n${name2} is ${condition}.\n"

文字列をダブルクォーテーションで囲んだ場合、文字列が含む変数は展開されます。

したがって上記のスクリプトを実行すると次のような出力が得られます。

John is ok.
Jack is ok.

・ シングルクォーテーション

シングルクォーテーションを使用すると変数の展開が行われません。

上記の例のダブルクォーテーションをシングルクォーテーションに置き換えると、次のような出力が得られます。

${name1} is ${condition}.
${name2} is ${condition}.

変数の展開が行なわれないので、変数名がそのまま表示されています。

・ バッククォーテーション

バッククォーテーションで囲む文字列は、たいてい何らかのコマンドです。

コマンドの実行結果として出力された内容に置換されます。

次のような例を考えてみましょう。

set result=`ls -la`
echo ${result}

バッククォーテーションで囲まれている文字列は、ファイル名一覧を出力する ls コマンドです。

result には ls によって出力された結果が代入されます。

つまりファイル情報一覧が代入されます。

この情報を使って何か処理を行なう…という利用方法があります。

◎ 単語リストと配列変数

・ 単語リスト

単語リスト(wordlist)とは、空白を区切り記号とした文字列の集合です。

例えば、

"Tokugawa Ieyasu" "Toyotomi Hideyoshi" "Oda Nobunaga"
find . -type f -maxdepth 2

前者のような空白で区切った人物名データの羅列や、後者のようなコマンド名といくつかの引数の並びは、どちらも単語リストといえます。

・ 配列変数

配列変数は単語リストをデータとして持ち、変数名と数字(インデクス)を組み合わせてアクセスできる変数です。

set array = ('Apple' 'Intel' 'Microsoft')
echo $array[1]
echo ${array[2]}
echo $array[1-2]
set i = 3
echo $array[$i]
echo ${#array}
echo $array
echo $array[*]

得られる出力は次のとおりです。

Apple
Intel
Apple Intel
Microsoft
3
Apple Intel Microsoft
Apple Intel Microsoft

変数に単語リストを代入するには、()を使います。

また、n 番目のデータを参照するには、

$変数名[n]
${変数名[n]}

のように指定して変数展開を行います。

インデクスが 1 から始まる点に注意してください。

$変数名[x-y]
${変数名[x-y]}

のように展開する範囲を指定することも可能です。

また、インデクスには変数を使うことができます。

$#変数名
${#変数名}

で配列変数の全要素数を得られます。

また、インデクス無しや、インデクスとして * を指定した場合は、空白区切りの単語リスト全体を得ることができます。

◎ 計算する

演算子を用いて数値演算を行うことができます。

演算を行うには、@ コマンドを使います。

@ 記号、変数、数値、演算子の前後には空白文字を区切り文字として入れます。

次に使用例を挙げます

@ var1 = 100 - 1
@ var2 = $var1 * 10
set var3 = 5
@ var3++
echo var1 var2 var3

得られる出力は次のようになります。

99 990 6

繰り返し申し上げますが、値を代入されるほうの変数名には $ を付ける必要がありません。

$ は変数の値を展開するための記号です。

また、変数のセクションでも述べましたが、

set x = 1
set y = 2
set z = ${x}+${y}

というような記述をしても、変数 z には "1+2" という文字列が代入されるだけで計算されません。

注意してください。

・ 演算子の種類

これらの演算子は、C言語など幾つかのプログラミング言語に触れたことのある方には馴染みのあるものでしょう。

わからない方は、C や Java、perl などの演算子について調べてみてください。

◎ スクリプトを書く上での注意

・ 行に気を付けよう

特に長くなりがちな単語リストを記述するときにで注意です。

set array=( "arekore" "hogehoge" "fugafuga" "hekoheko" (リターン)
"araaara" "korakora" "abekobe" "honyarara" (リターン)
"berabera" "perapera" )

これはエラーになります。

見易く書きたいときに上記のようにしがちです。

しかし改行文字があると、そこでその行の解釈は終了してしまいます。

このような場合は行末に backslash を入れて前後の行を接続しましょう。

set array=( "arekore" "hogehoge" "fugafuga" "hekoheko"\(リターン)
"araaara" "korakora" "abekobe" "honyarara"\(リターン)
"berabera" "perapera" )

・ 空白区切りをこころがけよう

どんなコマンドのときでも、ワード間になるべく空白をひとつ置くのがエラーを起こさないコツです。

◎ 制御構文

ある条件のもとで処理を分岐するときや、繰り返しを行うときに制御構文を用います。

・ 条件式

while や if は与えられた条件式を評価し、真(非0)であるか偽(0)であるかで処理を分岐させます。

条件式では、他のプログラミング言語と同様の演算子を使用することができます。

条件式に使用できる演算子は次のとおりです

文字列比較演算子以外は変数を数値として扱います。

( )
かっこ
~
ビットごとの否定(NOT)
!
論理否定(論理NOT)
* / % + -
積 除 剰余 和 差
<< >>
左シフト 右シフト
< <= > >=<
大小比較
== != =~ !~
文字列比較
&
ビットごとの論理積(AND)
^
排他的論理和(XOR)
|
ビットごとの論理和(OR)
&&
論理積(論理AND)
||
論理和(論理OR)

また、

-検査文字 ファイル名

という構文で、ファイルに関する条件も書くことが可能です。

主な検査文字は以下のとおりです。

r
読み出し可
w
書き込み可
x
実行可
e
ファイルが存在
o
ファイルのオーナーシップがある
z
ファイルサイズが0
f
一般ファイルである
d
ディレクトリである

・ while

メジャーなプログラミング言語の経験がある人にとってお馴染みの繰り返し構文です。

条件が成立している間、ループさせたいという場合に使用します。

while ( 条件式 )



end

条件式の評価結果が真である間、whileとendで囲まれた部分が繰り返し実行されることになります。

・ foreach

foreach 文は Perl 経験者には大変馴染み深いループ構文だと思います。

foreach は用意されたリストから値を 1 つずつ取り出し、1 ループずつ実行します。

リストが空になったら終了します。

foreach 変数 (リスト)



end

例えば次のように記述して使用します。

foreach var ("Neo" "Morpheus" "Trinity")
echo $var
end

この例の場合、Neo、Morpheus、Trinity の順に変数 var に代入され、end までのコマンド実行を繰り返します。

foreach で指定するリストに変数展開を含む場合は注意が必要です。

次のような例を考えてみます。

set users = ("Tokugawa Ieyasu" "Toyotomi Hideyoshi")
foreach name ($users)
echo $name
end

なんとなく、次のような出力が得られるような気がするかもしれません。

Tokugawa Ieyasu
Toyotomi Hideyoshi

しかし、単語リストを変数展開すると単語間を空白で区切った形式で展開されるため、上の例は次のスクリプトと同義となります。

foreach name (Tokugawa Ieyasu Toyotomi Hideyoshi)
echo $name
end

したがって、実際に得られる出力は次のようになり、期待通りの処理は行われません。

Tokugawa
Ieyasu
Toyotomi
Hideyoshi

これを解決するには、次のような記述が考えられます。

set users = ("Tokugawa\040Ieyasu" "Toyotomi\040Hideyoshi")
foreach name ($users)
echo $name
end
set users = ("Tokugawa Ieyasu" "Toyotomi Hideyoshi")
set i = 1
while ( $i <= $#users )
echo $users[$i]
@ i++
end

・ repeat

特定の回数だけコマンドを繰り返し実行するシンプルなコマンドです。

repeat 繰り返し数 コマンド

・ break

ループを脱出したいときに使います

while や foreach ブロック内で使用すると、そのループを終了させます。

・ continue

while や foreach ブロック内で、ループの先頭に戻りたいときに使います。

例えば foreach 内で使用すると、ループの先頭に戻り、単語リストの次の値を変数に入れて処理を続けることになります。

・ if

if ( 条件式 ) then


else if ( 条件式 ) then


else


endif

条件式の値が真であれば then の後のブロックを実行します。

そうでなければ else の後のブロックを実行します。(else は必須ではありません)

else の場合に再度 if を配置することもできます。

if のブロックは endif までです。

・ switch

switch ( 文字列 )
case 文字列:


breaksw

case 文字列:


breaksw

default:


endsw

() 内の文字列が case 文に後続するラベル文字列に一致したときに、その case ブロックを実行します。

1 つの case ブロックは breaksw が現れるまでです。

C にも switch 構文が存在しますが、C の switch が数値しか扱えないのに対し、こちらは文字列を比較することができます。

どの条件にも当てはまらない場合、default ラベル に後続するブロックを実行します。

switch ブロックは endsw までです。

◎ 外部コマンドを活用する

シェルスクリプトでは、シェル自身が持つ機能だけでなく外部コマンドを気楽に呼び出して使用できることが強みです。

外部コマンドを呼び出す場合、バッククォートを用いてコマンドの出力結果を得るだけでなく、コマンドの返す終了コードを参照することも 1 つの利用法です。

大抵のコマンドは実行に成功したか否かを終了コードとして返すよう実装されています。(「絶対に返す」「正確に返す」とは断言できません。)

最後に使用したコマンドが返す終了コードは、シェル変数 status に格納されます。($? でも終了コードを参照できます)

例えば次のように使用します。

set file1 = "./test1.txt"
set file2 = "./test2.txt"
diff ${file1} ${file2}
if( $status )
...
end

diff は 2 つのファイルの内容を比較し差分を出力するツールです。

上記の例では、diff がファイルの内容が同じ場合には 0 を、異なる場合には 1 を終了コードとして返すことを利用しています。

次に、利用頻度が高いであろう、文字列を加工できる外部コマンドをいくつか紹介します。

・ grep

grepは、ファイル中から条件にあう行だけを取り出すコマンドです。

条件に一致する行を取りだせたか否かで、異なる終了コードを返します。

・ sed

sedはストリームエディタです。

Perlのような正規表現を用いたパターンマッチや、バッファから数行切り出すといった処理に力を発揮します

・ awk

awk も sed 同様、文字列を細かく加工するのに役立ちます。

・ tr

tr は文字列中の特定の文字を削ったり置換したりするのに役立ちます。

◎ 変数の展開について

本来なら初期のほうで解説を行なうべきことですが、あえてこの段階で解説を行ないます。

代表的なものだけ挙げておきます

$?変数名
変数が定義されたものなのか未定義なのかを調べることができます。
$argv
シェルスクリプトに渡されたコマンドライン引数を単語リストで保持しています。$n(n:数字)でも各引数を参照できます。
$%変数名
変数が保持する文字列の長さを得られます。
$$
親シェルのプロセス番号
$<
標準入力から読みこんだ1行