Csh芸人のための備忘録
csh(Cシェル)はC言語likeに記述できるというコンセプトで開発されたシェルスクリプトである。世の中の主流はbashであるが、諸事情によりcshやtcshなどを使っているという人のために、忘れやすいポイントや注意点などを備忘録としてまとめておく。
なお当サイトでは個別の質問対応には応じられませんので、適宜、国内外の掲示板などをご活用下さい。
(管理人:blocks)
・目次
《基本的な使い方》
§ cshにおける変数の用法
§ cshにおける配列の用法
§ cshにおける配列のよくある間違い
● ● ●
《そもそもsyntax分からん系のTopic》
§ if文の書き方
§ for文の書き方
§ while文の書き方
・breakの使い方
§ 条件文の書き方
・ファイルやディレクトリの存在判定
・数値や文字列の比較
・配列の比較
§ インクリメントの書き方
● ● ●
《ラクをするために苦労する系のTopic》
(それぞれ別ページに飛びます)
§ revコマンド
§ cutコマンド
§ grepコマンド
§ sedコマンド
§ awkコマンド
● ● ●
《なんか死んだんですけど系のTopic》
§ 「@: Variable name must begin with a letter.」の怪
§ またお前か、「Subscript out of range.」
§ 「if: Empty if.」と「if: Expression Syntax.」
§ if文の条件式では小数が扱えない?
§ バックグラウンドプロセスが死ぬ病
【重要】cshのスクリプトが動かないとき
cshのスクリプトが何故か動かなくなることがある。そんなときは、まず以下のことを確認しよう。
①スクリプトの最終行に空行を入れてみる【重要】
②1行目にシェバン(#!/bin/csh)はある?
③実行権限はある?
④ファイルが開きっぱなしor保存中(busy状態)ではない?
⑤ファイルは本当に存在する?(名前は変更されてない?)
⑥ネットワークはちゃんと接続されている?
「そんな訳ないじゃん」という思い込みが思わぬ落とし穴になっていることも多い。上記のポイントは最初に確認しておかないと、こんなことに時間を費やしていたのかとガッカリすることになる。特に他人に相談する前に確認することをオススメします。
※それでも動かないときは何らかのエラーメッセージが出てくるはずなので、それをヒントに何とかするしかない。
cshにおける変数の用法
(例)変数 “str” に “apple” という文字列を代入する
変数を設定するときは set コマンドを使います。
↓ スクリプト例
1 2 3 4 5 6 |
#!/bin/csh set str="apple" echo $str |
↓ 実行結果
1 |
apple |
※「set str=XXX」で変数を呼び出す
※ set コマンドでは
set name=apple
set name = apple (+空白)
set name= “apple” (+ダブルクォーテーション)
set name = “apple“ (+空白+ダブルクォーテーション)
のいずれでも文字列を格納できる(シングルクォーテーション「’」でも同じ)
※ 環境によっては等号と変数名・文字列の間に空白を入れると格納されないことがあるので注意
● ● ●
(例)変数 “str” にコマンドの実行結果を代入する
コマンドの実行結果を変数に格納するときはバッククォート「」を使います。
↓ スクリプト例
1 2 3 4 5 6 |
#!/bin/csh set str=`date` echo $str |
↓ 実行結果
1 |
Tue Jan 28 20:05:14 JST 2020 |
※ 出力に空白(スペース)が含まれる場合は変数には配列として格納されます。これを知らないと意図しないバグの原因になることがあります。スペースで区切られている文字列は別々の要素として配列に格納される、ということは覚えておきましょう。
● ● ●
(例)変数 "str" を文字列中で呼び出す
echoコマンドで変数の中身を表示したいときはダブルクォーテーション「"」を使います。
↓ スクリプト例
1 2 3 4 5 6 |
#!/bin/csh set str="apples" echo "$str are red." |
↓ 実行結果
1 |
apples are red. |
※ シングルクォーテーション「'」で囲んではいけない("$str" は変数strの中身を文字列として表示したものを表すが、'$str' は「$str」という文字列そのものになってしまう)。例えば、"$name" は変数として機能するが、'$name' は単なる文字列としてしか扱われないので注意(各自試してみよう)。
● ● ●
《ポイント》変数名の後に別の文字列が続く場合は中カッコで括ろう
$name という変数があったとして、
1 |
cp /home/${user}/test/${name}.txt /home/${user}/csh_practice/ |
のように使う(いつも括っておいた方がバグ取りしやすい)。
$name が配列で要素の番号を指定しなければならない場合は、
1 |
cp /home/${user}/test/${name[$i]}.txt /home/${user}/csh_practice/ |
もしくは、
1 |
cp /home/${user}/test/$name[$i].txt /home/${user}/csh_practice/ |
cshにおける配列の用法
(例)配列 "fruits" に要素を格納する
↓ スクリプト例
1 2 3 4 5 6 7 8 9 |
#!/bin/csh set fruits=("apple" "grape" "lemon" "orange" "pineapple") echo $fruits echo $#fruits echo $fruits[1] echo $fruits[0] |
↓ 実行結果
1 2 3 4 |
apple grape lemon orange pineapple 5 apple |
※ ${配列名} = 配列の全要素を呼び出す
※ $#{配列名} = 配列の要素数
※ $配列名[1] = 配列の1番目の要素(最初の要素)
(cshでは配列の数え方が「1, 2, 3, ...」なので$fruits[0]には何も入っていない)
※ ダブルクォーテーションで囲まなくとも文字列は格納できるが、スペースを含む文字列は格納できないので注意。ダブルクォーテーション「"」もしくはシングルクォーテーション「'」で囲めばスペースを含む文字列でも一つの文字列として扱ってくれる。
● ● ●
(例)既存の配列 "fruits" に新しい要素を追加する
↓ 後ろに追加する例
1 2 3 4 5 6 7 8 |
#!/bin/csh set fruits=("apple" "grape" "lemon" "orange" "pineapple") set fruits=($fruits "melon") echo $fruits echo $#fruits |
↓ 実行結果
1 2 |
apple grape lemon orange pineapple melon 6 |
※ 前に追加する場合は
1 2 |
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言語のように中カッコで括る必要は無い。
1 2 3 |
if( 条件 ) then コマンド... endif |
複数の条件分岐がある場合はのようにする。他の言語に慣れてしまい、Shellにしばらく触れていないとif末尾の "then" を忘れがちなので注意。"then" と "endif" がそれぞれC言語の "{" と "}" に対応すると思っておけばよい。
1 2 3 4 5 |
if( 条件 ) then コマンド... else if( 条件 ) then コマンド... endif |
のようにする。他の言語に慣れてしまい、Shellにしばらく触れていないとif末尾の "then" を忘れがちなので注意。"then" と "endif" がそれぞれC言語の "{" と "}" に対応すると思っておけばよい。
● ● ●
※なお、bashでは以下のように条件を丸カッコではなく角カッコ [ ] で囲む。
1 2 3 4 5 |
if [ $hoge = $fuga ]; then echo "文字列は同じです" else echo "文字列は違います" fi |
For 文の書き方
実はcshには一般的な意味での「for文」は用意されておらず、すべてwhile文で記述することになる(bashではfor文もwhile文も使える)。
ただしcshでは「foreach文」というものが用意されており、以下のように書く。
1 2 3 4 5 6 |
foreach 変数名(引数1 引数2 ... 引数N) コマンド1 コマンド2 … コマンドN end |
例えば以下のプログラムを実行すると "xxx.sh" などのシェルスクリプトをテキスト形式に拡張子を変換してくれる。foreachにより $name の要素全てに対して「cp ${f}.sh ${f}.txt」のコマンドが実行される。
1 2 3 4 5 6 7 8 |
#!/bin/csh set name=(`find *.sh`) foreach f (${name}) cp ${f} ${f}.txt end |
※なお、bashだと5行目は「cp ${f} ${f%.sh}.txt」と書くことができる(便利だね!)。
シェルでコマンドを繰り返し実行するときは基本的にwhileを使う。
While 文の書き方
While 文の構文は以下。(中カッコは不要)
1 2 3 |
while( 条件 ) コマンド... end |
"while"の後の丸カッコ内に条件文を書く。"1 == 1" や "True" などと書けば無限ループになる。基本的な使い方は他の言語と変わらない。
以下のようにbreakを挟むと、条件2に合致した時点でwhileを1つ抜けることができる。
1 2 3 4 5 6 |
while( 条件 ) コマンド... if ( 条件2 ) then break endif end |
foreach文などと組み合わせれば、ファイルコピー先に同じ名前のファイルがあったら別の名前にして保存する、なんていう処理も可能。また、whileで任意の時間・回数だけ回してsleepを挟んでやるとバックグラウンド処理に使える。必須スキルです。
条件文の書き方
「ファイルやディレクトリが存在するか」や「文字列が一致するか」などで条件分岐したいときのためのTips。
何かが「存在するか」や「付与されているか」を判定したいときには以下の表現式を使う。
表現式 | 説明 |
---|---|
-d | ディレクトリ |
-f | 普通のファイル |
-r | 読み取りアクセス |
-w | 書き込みアクセス |
-x | 実行権 |
-e | ファイルの有無 |
-o | 所有権 |
-z | ファイルサイズがゼロ |
【使用例①】"test.txt" というファイルが存在するときに発動する
1 2 3 |
if( -f test.txt ) コマンド... end |
存在判定なので「-e」を使っても良い。
【使用例②】/home 直下に "work" という名前のディレクトリが存在しないときに作成する
1 |
if (! -d /home/work ) mkdir /home/work |
先頭の「!」は否定条件を意味する。「-d」だけだと「存在するならば」となり、「! -d」にすると「存在しないならば」となる。これも存在判定なので「-e」が使用可能。
● ● ●
数値の比較や、文字列の一致/不一致の判定についてはC言語ほど面倒ではないが、残念ながら「<」と「>」では実数の比較ができない(これの解決法は後述)。また、配列同士の一致/不一致の判定もループ処理が必要で面倒である。ただ、文字列の比較はまあまあシンプルに書ける。
【使用例③】$x と整数を比較する
1 2 |
set x = 5 if( $x > 1 ) echo "ok!" |
【使用例④】$x と $y の数値を比較する
1 2 3 |
set x = 5 set y = 3 if( $x > $y ) echo "ok!" |
【使用例⑤】文字列を比較する
1 |
set x = abc set y = abc if( $x == $y ) echo "ok!" |
例えば「set y = def」とすると何も表示されない。「set y = 1」と数値にしても何も表示されない(一応、文字列として比較されている?)。
【使用例⑥】配列を比較する
cshで配列を比較するためには、残念ながら要素を一つずつ比較する作業を強いられる。例えば以下のようにする。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#!/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までの整数の和を求めてみる。
1 2 3 4 5 6 7 8 9 10 11 12 |
#!/bin/csh set i = 1 set num = 0 while ($i <= 100) @ num = $num + $i @ i++ end echo $num |
↓ 実行結果
1 |
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」となり、正しく機能する。
1 2 3 4 5 6 7 |
#!/bin/csh if 1 == 1 then echo "aaa" endif |
「==」の部分を「<=」などの比較演算子に替えてもちゃんと動く。「!=」でも使える。
以下のif文も出力が「aaa」となり機能する。
1 2 3 4 5 6 7 8 9 |
#!/bin/csh set num1="1" set num2="10" if ($num1 <= $num2) then echo "aaa" endif |
では次の場合は・・・?
1 2 3 4 5 6 7 8 9 10 |
#!/bin/csh set num1="1.5" set num2="10" if ($num1 <= $num2) then echo "aaa" endif |
↓ 実行結果
1 |
if: Badly formed number. |
・・・というわけで、if文の条件式では小数を使うことはできません。実数の計算をしたいときはbcコマンドを利用しましょう。
数値計算の例)
echo "10.35 + 1.41" | bc -l
➥ 11.76
※ l オプション=「標準数学ライブラリを読み込む」
(gccでコンパイルするときと同じオプション)
数値比較の場合は不等式をechoすればよい。
1 |
echo "1.5 - 10.0 > 0" | bc -l |
↓ 実行結果
1 |
0 |
これで正誤判定ができるので、
1 2 3 4 5 6 7 8 9 10 |
#!/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コマンドを付けていなかったことによるものでしょう。ログアウトしても動作し続けるようにしたい場合は以下のようにします。
1 |
nohup ./xxx.sh & |
それ以外の場合は何らかのエラーによってプロセスが異常終了したというケースが該当します。何とかして原因を突き止めて下さい・・・。