csh 入門 ---- file 入出力

プログラムが複雑になってくると、 どうしても作業 file に書き出すことが必要です。 ここでは、file の入出力をまとめます。便利な部品を 紹介しましょう。7.3 は、非常に長いので、興味のある人は 印刷して、じっくり検討してみて下さい。

7.1 file への出力

file への出力記号は いろいろあります。まず kterm で試してみ てください。

  1. % command > file   file を作り出力。file があるとエラー
  2. % command >! file    file を強制的に作り出力。
  3. % command >> file    file に追加して出力。
  4. % command >& file    エラー表示を file に出力
  5. % command >>& file    エラー表示をfile に追加
注意: command > file で file があると file: File exists とい う エラーがでるのは、noclobber という 変数が set されている ときだけです。過って file を消さないように、set noclobber が 標準で ~/.cshrc で設定されていることが多いです。% set とする と、現在設定されている変数が分かります。
% touch toto          toto という file を作ります。
% ls -l toto          大きさは 0 byte です。
-rw-r--r--   1 rsaito   bin             0 Jul  7 21:24 toto
% set noclobber       noclobber を set します。
% ls -l > toto        エラーメッセージがでます。
toto: File exists.  
% unset noclobber     noclobber を解除 unset します。
% ls -l > toto        今度は エラーがでなくなりました。
% 
一度に多くの人が同時にコマンドを使う可能性は unix の場合あり ます。プログラムで 同じfile 名に出力すると、同時に動いた場合 正しく動かない場合があります。それを避けるために $$ を使いま す。
#!/bin/tcsh
#                  7.1 file への出力
ls -l >! toto.$$
ls -l toto.$$
cat toto.$$

これを実行すると、toto.2323 のように toto と 数字のついた file ができます。この数字は、このプログラムが動いた process ID です。したがって同じになるためには、process ID が 一巡 しないと起こりません。一巡したときたまたま同じプログラムが 同じ process ID を持つ確率は非常に小さいので安全な方法です。 同時に動いても支障がありません。実際には、このような作業 file は、プログラムが終了する前に消すことをお勧めします。 作業 file は 小さければ /tmp directory に作っても ok です。 Unix で /tmp を memory に設定している場合には、処理が高速に なります。ただし /tmp は、みんなの共通の場所ですので、使った ら必ず消してください。

7.2 lock file をもちいた制御 lock

こんどは、commando がどうしても重複して動いてはいけない場合 があります。例えば先着順を決めるような場合です。複数の host が 一つの file サーバで動いている場合には、処理を開始するの が先だからといって、先に終るかどうか保証できません。また 一 つの file に書き込み処理をする場合には、後先の問題が生じます。 そのため lock (鍵) file を作って、lock file が終るまで、次の コマンドを待っているように設定します。
#!/bin/tcsh
#                  7.2 lock file をもちいた制御 lock
#
# /tmp/a.lock がある限り、while ... end の間を繰り返す。
while ( -e /tmp/a.lock )
# 2 秒待ちます。
sleep 2
end
# もし a.lockock が無ければ、作業をする前に file を作る
touch /tmp/a.lock
# 誤動作があっても誰でも消すことができるようにします。
chmod 777 /tmp/a.lock
# ここから 実際の作業
echo "作業を始めます。10 秒お休みです。"
sleep 10
echo "作業を終ります。"
# 最後に lock file を忘れずに消します。
rm /tmp/a.lock

このプログラムの作業は、実際の間を持たせるために、10 秒 お休みしています。このようにすれば、lock が 一度に 2 度 動いても、先に動いた人が終るまでは、次の人が動くことはありま せん。ただし、3 人以上同時に コマンドをいれた場合には、 1 番目の人が終ったとき、次に動くのは、2 秒待ちの部分が先に終っ た人になります。しかし、メール等を受け取るようなものでしたら、 これで十分です。厳密に先着順を作る場合には、待ちリストを作る ことになります。どうぞ皆さん待ちリストを作ってみてください。

待ち行列の最後に追加するのは >> で簡単ですが、csh では 1 行 づつ file から読んで処理することができません。(少なくても私 は知りません。) そこで、start から end まで切り出すようなこ とを awk や sed で 行います。(前のところを みてください。) 一行読むのに便利なコマンドは perl の <> コマンドです。興味の あるひとは perl 入門を見てください。でも多くの場合には、1 行 づつ読んで処理することはあまり必要ありません。

7.3 自動処理・返信メール

スクリプトの仕上げとして、メールを自動的に読んで、処理し 結果を返信する、一連の処理を スクリプトにした例を示します。 自動集計、アンケート、自動採点、意志表示、宣言、等応用例は 非常に多いです。注意したいのは、返信 e-mail を出しますので、 e-mail の規約の中身がないといけません。ここに示したのは、 e-mail の標準的な規約に従っています。ですので、使う場合には そのまま使ってください。

プログラムは、(1) 自動処理をする sewanin2 と (2) 自動返信を する replymail2 からなります。sewanin2 から replymail2 を読ん でいます。replymail2 を呼ぶ場合には、送られた e-mail を解読 して 環境変数として、REPLY(返送先), SENDER (送り主)、FROM (誰から) の 3 つの変数から、BACK (自動返送先) を選択してい ます。replaymail2 を単独で使う場合には、この $BACK を setenv で replaymail2 の中で設定する必要があります。

さらに sewanin2 の中で、getitem (項目を取り出す)、getright (項目の右部分を取り出す) プログラムを読んでいます。これらも 紹介しましょう。プログラムを分解すると、デバッグがしやすいで す。欠点は変数を受け継がせるために、環境変数の設定が必要です。

また e-mail を sewanin2 に読ませるには、~/.foward という file をつくってそこに

"| ~/bin/sewanin2"

のような設定が必要です。この場合普通の e-mail もこれに処理去 れてしまいますので、分けたい場合にはさらに工夫が必要です。 通常は、自動処理専用の e-mail address を持つ方が簡単です。

以下はスクリプトの中で説明します。初心者の人はこの先を見なく ても ok です。実際に動かすには、沢山の file を用意しなければ いけません。これを自動的に作る script も実際には用意されてい ます。ですが、こんなに複雑に設定しなくても、一部分を利用する ことは可能ですので、あきらめず読んでみてください。


#!/bin/tcsh
#
# メールの自動管理プログラム。
#                   ver. 1.00 by Ryouma Matsuo. 97/12/19
#                        1.01 by R. Saito       97/12/20

#  AR は処理する プログラムがはいっている場所です。
set AR = "/local2/auto-reply"

# 処理に必要な path を設定しています。
set path =(. $AR /usr/ucb /bin /usr/bin /usr/local/bin )

# 作られる file の属性は 777 にします。
umask 000

# 作業 directory に移動します。
cd $AR

# sewanin.lock がある間は処理しません。
while ( -e /tmp/sewanin.lock )
sleep 2
end

# sewanin.lock がなければ作業開始です。
touch /tmp/sewanin.lock
chmod 777 /tmp/sewanin.lock

# 初期化をします。

# 途中で C-c がかかった場合には、作業 file を消去する END に
# いきます。 
onintr END

# 作業 file の設定です。
set tmpf=/tmp/oshigoto$$.tmp1
set tmpf2=/tmp/oshigoto$$.tmp2
set DATE=`date`

# ここで まず e-mail の中身が全て $tmpf にはいります。
nkf -e >! $tmpf

# 次に start と end と書いた部分を切り出します。tmpf2 に書き
# 出します。ここの間にあるものだけを処理します。
awk  /start/,/end/ < $tmpf > $tmpf2 
#sed -n '/^start/,/^end/p' $tmpf > $tmpf2

# 返送先の読みだしを $tmpf から行います。検索文字(Reply-to:) を探してい
# ます。大文字小文字を区別しません。結果を環境変数にいれます。
setenv REPLY `sed -n 's/^[Rr][Ee][Pp][Ll][Yy]-[Tt][Oo]: \(.*\)$/\1/p'` < $tmpf
setenv SENDER `sed -n 's/^[Ss][Ee][Nn][Dd][Ee][Rr]: \(.*\)$/\1/p'` < $tmpf
setenv FROM `sed -n 's/^[Ff][Rr][Oo][Mm]: \(.*\)$/\1/p'` < $tmpf

# ここで優先順位をつけて返送先を選択します。
setenv BACK2 $REPLY
if ( "$BACK2" == "" ) setenv BACK2 "$FROM"
if ( "$BACK2" == "" ) setenv BACK2 "$SENDER"
if ( "$BACK2" == "" ) goto END

# 最終的な返送先として BACK を選択します。
setenv BACK `echo $BACK2 | sed 's/^.*<\(.*\)>.*$/\1/' | awk -F' ' '{print $1}'`
# echo $BACK

# 送られて来る手紙は以下のような構成になっているものとします。
# 用件(jobname)をお仕事名として、
# 登録したお仕事のみ処理をするようにします
# また項目は、処理する側から書式として設定したものです。
# 項目については下に説明があります。

# start
# jobname お仕事名
# 項目1  項目1の中身
# 項目2  項目2の中身
# .......
# end
# 

# まずはお仕事名の取り出しです。
setenv JOBNAME `awk -F' ' '$1 ~ /[Jj][Oo][Bb][Nn][Aa][Mm][Ee]/ {print $2;exit}' $tmpf2`

# お仕事名が無い場合には、no-jobname という message が
# かかれた No-JOB.mes という file を送り返します。
if ( "$JOBNAME" == "") then
# 念のため 切り出した部分 ($tmpf2) もつけます。
replymail2 $AR/Messages/No-JOB.mes $tmpf2 
goto END
endif

#
# 以下お仕事名のある空間 $AR/job/$JOBNAME にいって、お仕事をします。
# job 以下の directory と file を作ります。$JOBNAME という 
# directory にいって作業をします。
#
# お仕事の仕様は $JOBNAME.job にかかれています。
#
if (-e job/$JOBNAME/$JOBNAME.job) then
cd job/$JOBNAME
else

# お仕事名が登録されていない場合です。
# Bad-JOB.mes という message を返します。
# 念のため 切り出した部分 ($tmpf2) もつけます。
replymail2 $AR/Messages/Bad-JOB.mes $tmpf2 
goto END
endif

# お仕事の仕様 (1) です。
# もし $AR/job/$JOBNAME/$JOBNAME.job に
# userlist: YES 
# と 1行かかれていたら、job/$JOBNAME/$JOBNAME.lst という file が
# 必ず無ければいけません。ここに受け取ることができる user の 
# e-mail を 記入していれば、その人からしか処理しません。
# 
set USERLIST=`awk -F' ' '$1 ~ /[Uu][Ss][Ee][Rr][Ll][Ii][Ss][Tt]:/ {print $2;exit}' < $JOBNAME.job`
if ( "$USERLIST" == "YES" ) then
   set EXIST=`grep -i $BACK $JOBNAME.lst`
  if ( "$EXIST" == "" ) then 

# もし 返送者 $BACK がリストになければ、No-User.mes と
# 切り出したところ ($tmpf2) をつけてお返事します。
     replymail2 $AR/Messages/No-User.mes $tmpf2 
     goto END
   endif
endif

#
# さて処理をしてよい人だけがここまで来ることができます。
# 処理の前に e-mail が届いたことを記録します。
#
#log の記録
echo ---- $DATE ---- $BACK >> $JOBNAME.log

# お仕事の仕様 (2) です。
# $JOBNAME.job という file には、
# items: 項目1, 項目2, 項目3
# という行が設定できます。送られて来た e-mail の中には
# この項目に相当する行があることを期待します。つまり
# e-mail の format で指定されたものです。上の format を見て
# ください。
set ITEMS=`awk -F' ' '$1 ~ /[Ii][Tt][Ee][Mm][Ss]:/ {print $0;exit}' < $JOBNAME.job`

# 項目名の集まりを切り出します。items: のみ取り除きたいので
# shift コマンドを使います。
shift ITEMS

# 項目を 1 個 1個 読みだします。
foreach i ( $ITEMS )
#set TMP=`awk -F' ' '$1=='\"$i\"'{print $0;exit}' $tmpf2`

# getitem : 項目の取り出すコマンドです。そのための
# 環境変数 GETITEM (項目名) GETITEMF (項目名があるところ)
# を設定します。
setenv GETITEM "$i"
setenv GETITEMF $tmpf2
setenv TMP `getitem` 
# 項目の 1 行の結果は TMP に入れられます。これも念のため
# log に入れられます。答案用紙を保全します。
echo "$TMP" >> $JOBNAME.log

setenv GETRIGHT "$TMP"

# 項目の中身は getright で取り出します。答えをとりだします。
setenv $i `getright`

# さらに項目の中身で処理をしたい場合には この $i を処理しま
# す。

#if ( "$i"=="" ) setenv $i "-"
# この end は foreach に対する end です。全ての項目に対して
# 繰り返します。
end

# お仕事の仕様 (3) です。
# $JOBNAME.job という file に、
# checklist: YES
# があれば、$JOBNAME.chk という file があって、e-mail を受け
# 取った人の e-mail address を削除します。
# 同じ人から 2 度とることはありません。また file に残ってい
# る人がまだ送っていない人です。催促は送っていない人だけに
# すべきだと思います。
#Check list より削除
set CHECKLIST=`awk -F' ' '$1 ~ /[Cc][Hh][Ee][Cc][Kk][Ll][Ii][Ss][Tt]:/ {print $2;exit}' < $JOBNAME.job`
if ( -e $JOBNAME.chk) then
 if ( "$CHECKLIST" == "YES" ) then
 mv $JOBNAME.chk $JOBNAME.chk$$
 awk -F' ' '$1 !~ /'$BACK'/'  > $JOBNAME.chk < $JOBNAME.chk$$
 endif
endif

# お仕事の仕様 (4) です。
# $JOBNAME.job という file に、
# subprg: filename
# というものがあれば、項目で設定した $i を使って、処理ができ
# ます。答案の採点などが考えられます。

#更に処理する場合のプロセス起動
set SUBPRG=`awk -F' ' '$1 ~ /[Ss][Uu][Bb][Pp][Rr][Gg]:/ {print $2;exit}' < $JOBNAME.job`
 if ( "$SUBPRG" != "" ) then
  "$SUBPRG"
 endif

#
# これで全て終了です。もし結果を返すのであれば、したで
# 返事をだします。
#
replaymail2 complete.mes $kekka $tempf

#
# 最後にどんな場合でも 作業 file を消去します。
#
END:
rm -f $JOBNAME.chk$$
rm -f $tmpf
rm -f $tmpf2
rm -f /tmp/sewanin.lock

ずいぶん長いスクリプトですね。これを作った松尾君は私の研究室 の学生ですが、大部時間がかっかったと文句をいっていました。 では引続き replymail2 をみましょう。
#!/bin/csh

# もし replymail2 を単独に使う場合には $BACK に
# 返送 e-mail address を入れる必要があります。
#
# setenv BACK who@doko.koko.jp
#
# 途中 C-c があっても作業 file は消します。
onintr END
# PNAME に 自分の e-mail address をいれます。
set PNAME=sewanin
# 作業 file です。
set tmpf=/tmp/rep$$.mail

echo "Return-Path: " >> $tmpf
# もし エラーがあった場合戻る先を設定します。
echo "Errors-To: ryouma@tube.ee.uec.ac.jp" >> $tmpf
#                @@@@@@@@@@@@@@@@@@@@@@@@ ここを直してください。
echo "Reply-To: "$PNAME@`hostname` >> $tmpf
# Subject の中身も直すとよいと御読みます。
echo "Subject: Message from Auto-Replier." >> $tmpf
echo "-----" >> $tmpf
# ここに replymail2 の引数全部の内容を e-mail にいれます。
cat $* >> $tmpf
echo "-----" >> $tmpf
# ここ以下は signature です。これも使う場合には直してくださ
# い。
echo "     University of Electro-Communications" >> $tmpf 
echo "     Department of Electronics Eng. Kimura-Saito Lab." >> $tmpf
echo "          Auto-Reply Program" >> $tmpf
echo "     By ryouma@tube.ee.uec.ac.jp" >> $tmpf

# 最後に、日本語の e-mail は JIS(7bit) で送らないといけませ
# んから nkf で変換して、$BACK あてに rmail でおくります。


nkf -j $tmpf | rmail $BACK

# 作業 file を消去します。
END:
rm -f $tmpf


あとは sewanin2 で使われている getitem と getright を紹 介します。このシステムを正確に動かすためには、このスクリプト を十分理解して、directory や file を作る必要があります。研究 室用には 簡単 manual を作りましたが、メモ程度です。 是非どなたか作ってください。この software が free (いわゆる beer-ware) ですが、著作権があり利用する場合には、許可を必要 とします。営利で(会社内 e-mail を含む) 利用する場合には、 raito@ee.uec.ac.jp, またはryouma@flex.ee.uec.ac.jp に e-mail で承諾を得る必要があります。以下は説明無しで紹介します。
#!/bin/csh
# getitem コマンド
awk -F' ' '$1=='\"$GETITEM\"'{print $0 ;exit}' $GETITEMF


#!/bin/csh
# get right コマンド
echo "$GETRIGHT" | awk '{ for(i=2;i<=NF;i++) printf("%s ",$i) } '

rsaito@ee.uec.ac.jp