CシェルTips集(Csh芸人のための備忘録)


Csh芸人のための備忘録

csh(Cシェル)はC言語likeに記述できるというコンセプトで開発されたシェルスクリプトです。世の中の主流はbashですが、諸事情によりcshやtcshなどを使っているという人のために、忘れやすいポイントや注意点などを備忘録としてまとめておきます。

なお当サイトでは個別の質問対応には応じられませんので、適宜、国内外の掲示板などをご活用下さい。

(管理人:blocks)


 

 【重要】cshのスクリプトが動かないとき

cshのスクリプトが何故か動かなくなることがあります。そんなときは、まず以下のことを確認しましょう。

①スクリプトの最終行に空行を入れてみる
②不正な改行コードが混入していないか?
③1行目にシェバン(#!/bin/csh)はある?

④実行権限はある?
⑤ファイルが開きっぱなしor保存中(busy状態)ではない?
⑥ファイルは本当に存在する?(名前は変更されてない?)
(⑦ネットワークはちゃんと接続されている?)

「そんな訳ないじゃん」という思い込みが思わぬ落とし穴になっていることも多い。上記のポイントは最初に確認しておかないと、こんなことに時間を費やしていたのかとガッカリすることになる。特に他人に相談する前に確認することをオススメします。

※それでも動かないときは何らかのエラーメッセージが出てくるはずなので、それをヒントに何とかするしかありません。

 

 cshにおける変数の用法

(例)変数 “str” に “apple” という文字列を代入する

変数を設定するときは set コマンドを使います。

#!/bin/csh

set str="apple"
echo $str
apple

※「set str=XXX」で変数を呼び出します。
※ set コマンドでは
set name=apple
set name = apple (+空白)
set name= “apple” (+ダブルクォーテーション)
set name = apple (+空白+ダブルクォーテーション)
のいずれでも文字列を格納できます。シングルクォーテーション「 ‘ 」でも同じ。
※ 環境によっては等号と変数名・文字列の間に空白を入れると格納されないことがあるので注意。

●   ●   ●

(例)変数 “str” にコマンドの実行結果を代入する

コマンドの実行結果を変数に格納するときはバッククォートを使います。

#!/bin/csh

set str=`date`
echo $str
Tue Jan 28 20:05:14 JST 2020

※ 出力に空白(スペース)が含まれる場合は変数には配列として格納されます。これを知らないと意図しないバグの原因になることがあります。スペースで区切られている文字列は別々の要素として配列に格納される、ということは覚えておきましょう。

●   ●   ●

(例)変数 “str” を文字列中で呼び出す

echoコマンドで変数の中身を表示したいときはダブルクォーテーション「”」を使います。

#!/bin/csh

set str="Apples"
echo "$str are red."
Apples are red.

※ 変数はシングルクォーテーション「’」で囲んではいけません。($str は変数strの中身を文字列として表示したものを表しますが、'$str'$str」という文字列そのものになってしまいます)
※ 例えば、$name は変数として機能しますが、'$name' は単なる文字列としてしか扱われないので注意が必要です。

●   ●   ●

《ポイント》変数名の後に別の文字列が続く場合は中カッコ(波カッコ)で括る

$name という変数があったとして、

cp  /home/${user}/test/${name}.txt  /home/${user}/csh_practice/

のように記述します(いつも括っておいた方がバグ取りがラクです)。

$name が配列で要素の番号を指定しなければならない場合は、

cp  /home/${user}/test/${name[$i]}.txt  /home/${user}/csh_practice/

もしくは、

cp  /home/${user}/test/$name[$i].txt  /home/${user}/csh_practice/

のようにします。

 

 cshにおける配列の用法

(例)配列 “fruits” に要素を格納する

#!/bin/csh

set fruits=("apple" "grape" "lemon" "orange" "pineapple")
echo $fruits
echo $#fruits
echo $fruits[1]
echo $fruits[0]
apple grape lemon orange pineapple
5
apple

※ ${配列名} = 配列の全要素を呼び出す
※ $#{配列名} = 配列の要素数
※ $配列名[1] = 配列の1番目の要素(最初の要素)
cshでは配列の数え方(index)が 1, 2, 3, … なので$fruits[0]には何も入っていない
※ ダブルクォーテーションで囲まなくとも文字列は格納できるが、スペースを含む文字列は格納できないので注意。ダブルクォーテーション「”」もしくはシングルクォーテーション「’」で囲めばスペースを含む文字列でも一つの文字列として扱ってくれる。

●   ●   ●

(例)既存の配列 “fruits” に新しい要素を追加する

#!/bin/csh

set fruits=("apple" "grape" "lemon" "orange" "pineapple")
set fruits=($fruits "melon")
echo $fruits
echo $#fruits
apple grape lemon orange pineapple melon
6

※ 前に追加する場合は

set fruits=("apple" "grape" "lemon" "orange" "pineapple")
set fruits=("melon" $fruits)

とします。
※ $#{配列名} = 配列の要素数
※ $配列名[1] = 配列の1番目の要素(最初の要素)
(cshでは配列の数え方が「1, 2, 3, …」です)

 

 cshの配列でよくある間違い

(以下、”array” は配列名とします)

    • よくある間違い①:{}の外にindexを書いてしまう

(ダメな例)${array}[1]

csh側では ${array} と [1] を別々に読み取るので、これは「${array}に[1]を付け加えたもの」となってしまいます。


    • よくある間違い②:[0]から始めてしまう

$array[0] ≠ 配列の1番目の要素

cshでは配列が 1, 2, 3, … と番号付けされます。なぜかここはC言語likeではない…。


    • よくある間違い③:カンマで区切ってしまう

(ダメな例)set array=(“aaa”, “bbb”, “ccc”)

cshでは必ず空白で要素を区切らなければなりません。久々にcshに触れたときによくあるミスです。

 

 if 文の書き方

cshにおける If 文の構文は以下。なお、C言語のように中カッコで括る必要はありません。

if( 条件 ) then
    コマンド...
endif

複数の条件分岐がある場合は以下のようにします。Cシェルにしばらく触れていないとif末尾の “then” を忘れがちなので要注意。”then” と “endif” がそれぞれC言語の “{” と “}” に対応すると思っておけばよいでしょう。

if( 条件 ) then
    コマンド...
else if( 条件 ) then
    コマンド...
endif

●   ●   ●

※なお、bashでは以下のように条件を丸カッコではなく角カッコ [  ] で囲みます。

if [ $hoge = $fuga ]; then
    echo "文字列は同じです"
else
    echo "文字列は違います"
fi

※条件文の詳しい書き方については後述します

 

 For 文の書き方

実は、cshには一般的な意味での「for文」は用意されておらず、すべてwhile文で記述することになります(bashではfor文もwhile文も使える)。

ただしcshでは「foreach文」というものが用意されており、以下のように書きます。

foreach 変数名(引数1 引数2 ... 引数N)
    コマンド1
    コマンド2
    …
    コマンドN
end

例えば以下のプログラムを実行すると “xxx.sh” などのシェルスクリプトをテキスト形式に拡張子を変換してくれます。foreachにより $name の要素全てに対して「cp ${f}.sh ${f}.txt」のコマンドが実行されます。

#!/bin/csh

set name=(`find *.sh`)
foreach f (${name})
    cp ${f} ${f}.txt
end

※なお、bashだと5行目は「cp ${f} ${f%.sh}.txt」と書くことができるので便利。

シェルでコマンドを繰り返し実行するときは基本的にwhileを使います。

 

 While 文の書き方

While 文の構文は以下。中カッコは不要です。

while( 条件 )
    コマンド...
end

“while”の後の丸カッコ内に条件文を書きます。”1 == 1” や “True” などと書けば無限ループになります。基本的な使い方は他の言語と変わりません。

以下のようにbreakを挟むと、条件2に合致した時点でwhileを1つ抜けることができます。

while( 条件 )
    コマンド...
    if ( 条件2 ) then
       break
    endif
end

foreach文などと組み合わせれば、ファイルコピー先に同じ名前のファイルがあったら別の名前にして保存する、なんていう処理も可能です。また、whileで任意の時間・回数だけ回してsleepを挟んでやるとバックグラウンド処理に使えます。作業を自動化する上で必須のスキルです。

 

 条件文の書き方

「ファイルやディレクトリが存在するか」や「文字列が一致するか」などで条件分岐したいときのためのTipsを紹介します。

何かが「存在するか」や「付与されているか」を判定したいときには以下の表現式を使います。

表現式 説明
-d ディレクトリ
-f 普通のファイル
-r 読み取りアクセス
-w 書き込みアクセス
-x 実行権
-e ファイルの有無
-o 所有権
-z ファイルサイズがゼロ

【使用例①】”test.txt” というファイルが存在するときに発動する

if( -f test.txt )
    コマンド...
end

※ 存在判定なので「-e」を使ってもOKです


【使用例②】/home 直下に “work” という名前のディレクトリが存在しないときに作成する

if (! -d /home/work ) mkdir /home/work

先頭の「!」は否定条件を意味します。「-d」だけだと「存在するならば」となり、「! -d」にすると「存在しないならば」となります。これも存在判定なので「-e」が使用可能です。

●   ●   ●

数値の比較や、文字列の一致/不一致の判定についてはC言語ほど面倒ではありませんが、残念ながら「<」と「>」では実数の比較ができません(これの解決法は後述)。

また、配列同士の一致/不一致の判定もループ処理が必要でやや面倒です。ただ、文字列の比較はまあまあシンプルに書けます。


【使用例③】$x と整数を比較する

set x = 5
if( $x > 1 ) echo "ok!"

【使用例④】$x と $y の数値を比較する

set x = 5
set y = 3
if( $x > $y ) echo "ok!"

【使用例⑤】文字列を比較する

set x = abc
set y = abc
if( $x == $y ) echo "ok!"

ここで例えば「set y = def」とすると何も表示されません。「set y = 1」と数値にしても何も表示されません。


【使用例⑥】配列を比較する

cshで配列を比較するためには、残念ながら要素を一つずつ比較する作業を強いられます。例えば以下のように書けます。

#!/bin/csh

set name1=(aaa bbb ccc)
set name2=(aaa bbb ccc)
set i = 1
foreach f ($name1)
if ($f == $name2[$i])
    @ i++
end

if( $i == $#name2 + 1 ) echo "ok!"

もっとスマートな書き方がありそうな気もしますが…。こういう所はC言語likeですね(笑)。

 

 インクリメントの書き方

i という変数でループの回数を制御する場合は「@ i++」のようにします。あるいは「@ i = $i + 1」としても良いでしょう(この書き方をするときは右辺の変数に$を付けるのを忘れないこと)。

ここでは1から100までの整数の和を求めてみます。

#!/bin/csh

set i = 1
set num = 0
while ($i &lt;= 100)
    @ num = $num + $i
    @ i++
end

echo $num
5050

数値の足し引きを行うときは行の先頭に「@」を付ける必要があります。これを忘れる人が多いです。また、整数型変数の宣言ではsetコマンドではなく「@ i = 1」、「@ num = 0」などと宣言することもできます。

 

「@: Variable name must begin with a letter.」の怪

変数名が数字から始まっていたりすると突如として出現するエラー。しかし変数名はアルファベットから始まっている…。ではこれは何のエラーだ!?

結論から言うと、大抵の場合、@でint型の変数を宣言する方法が間違っています。例えば配列の要素数に1だけ加えた整数値を定義したいとき、本来であれば

「@ num = $#array + 1」

と書くべきところを、

「@ num = $#array +1

と書いてしまったのが原因です。一体どこが違うんだ??…という感じですが、正しくは「+」と「1」の間に空白が必要です。(こんなんでエラーを吐かれるなんて聞いてない・・・)

因みに「+」に限らず、演算子と数字の間には空白が必要です。気を付けましょう。CやPythonでは問題なく宣言できるのに、Rubyとかでもこういうエラーを吐かれることがある。いい加減にしてくれ。

 

 またお前か、「Subscript out of range.」

配列を扱ったことのある人なら必ず出くわすであろう「Subscript out of range.」のエラー。文字通り配列の要素外を参照していることが原因なのは見れば分かるのですが、犯人は誰・・・?という状態に陥ることがしばしば。どの配列でエラーになっているのかまで教えてくれない場合はバグ取り作業が非常に面倒。配列の要素数をしっかり数えておかないと信じられないバグを生み出してしまう可能性大です。

具体的な解決方法としては、主に直近に追加した部分で

①ループを回している変数の初期化を忘れていないか確認する
②変数の初期化が本当に正しいか確認する
③ちゃんと配列に要素が追加されているか確認する
④ループ回数の上限が配列の要素数と合っているか確認する

などの点について何度も確認することをオススメします。それでも解決しない場合はコメントアウトしたりecho文を挟みつつ、該当箇所をシラミ潰しに探していくしかありません…。

他人のプログラムをいじっているときにこれが出てきたときの絶望感は是非皆さんにも味わって欲しい。(訳:コメント文はちゃんと書こうね)

 

「if: Empty if.」と「if: Expression Syntax.」

if: Empty if.」は if の後に “then” が無いことによるエラー。なお、ワンラインif文の場合は “then” は不要。

if: Expression Syntax.」はifの条件文の文法に誤りがあるときに出現するエラー。文字列と数値など、型の異なる変数を比較していたりすると発生します。

しばらくcshのコードを書いていないと「then: then/endif not found.」などという可愛いエラーも時々顔を見せます。数あるエラーの中では癒し系の部類。

 

 if文の条件式では小数が扱えない?

これは主に大小比較をしたいときのTipsです。

以下のif文は出力が「aaa」となり、正しく機能します。

#!/bin/csh

if 1 == 1 then
    echo "aaa"
endif

「==」の部分を「<=」などの比較演算子に替えてもちゃんと動きます。「!=」でもOK。以下のif文も出力が「aaa」となり機能します。

#!/bin/csh

set num1="1"
set num2="10"

if ($num1 <= $num2) then
    echo "aaa"
endif

では次の場合は・・・?

#!/bin/csh

set num1="1.5"
set num2="10"

if ($num1 <= $num2) then
    echo "aaa"
endif
if: Badly formed number.

・・・というわけで、if文の条件式では小数を直接比較することができません。実数の計算をしたいときはbcコマンドを利用しましょう。

数値計算の例) 
echo "10.35 + 1.41" | bc -l

➥   11.76
※ l オプション=「標準数学ライブラリを読み込む」
(gccでコンパイルするときと同じオプション)

数値比較の場合は不等式をechoすればよい。

echo "1.5 - 10.0 > 0" | bc -l
0

これで正誤判定ができるので、

#!/bin/csh

set num1="1.5"
set num2="10.0"

if ( `echo "$num1 - $num2 > 0" | bc -l` == 1 ) then
    echo "aaa"
endif

などと書けば期待通りの動作になる。ここで

「if ( `echo “{不等式}” | bc -l` == 1 )」

「{不等式}が成り立てば」

の意味になっている。「!=」にすれば逆のときに発動する。

これで小数を含めた数で大小比較ができるようになる。めでたしめでたし・・・。

 

 バックグラウンドプロセスが死ぬ病

バックグラウンドでコマンドを実行する場合は末尾に「&」を付けて実行します。「ps x」でプロセスを見るとちゃんと裏で動いているのが確認できます。

ログアウトするとバックグラウンドプロセスが死ぬ場合、これは単純にnohupコマンドを付けていなかったことによるものでしょう。ログアウトしても動作し続けるようにしたい場合は以下のようにします。

nohup ./xxx.sh &

それ以外の場合は何らかのエラーによってプロセスが異常終了したというケースが該当します。何とかして原因を突き止めて下さい・・・。