得点集計システムの自動化(SMTP経由)

コマンドライン操作で主要なデータ操作ができるようになった シェルスクリプトを、電子メイル経由で操作するようにして、 ある程度の自動採点システムを構築する手法を考える。

作成システムの前提

今回作成するものは、以下のような流れで使用するものの一部とする。

  1. 解答者は所定のFromアドレスから指定したアドレスにメイル送信する。
  2. 送信により自動受信スクリプトが動く。
  3. 受信スクリプトは採点プログラムを呼び 得られた点数をデータベースに登録する。

このうち「採点プログラム」は統一的に決められるものではないので作らず、 今回は宛先によって得点を決める単純なものとする。 たとえば、解答者に出された条件は以下のようなものとする。

つまり、宛先によって処理を切り替えるようにする。このような方法であれば 投票システムなどにも応用できる。ちなみにメイル本文で処理を切り替える方法は、 送信形態の違い(テキストメイルかHTMLメイルか)や、 表記ゆれへの対応が必要で、精度の高いものを作ることは容易でない。

このような条件で送られた課題提出メイルを自動受信スクリプトが受け取る。 これは以下の条件で動くものとする。

この条件で、受信スクリプト receiver.sh を作成する。

受信スクリプト稼動に必要な準備

軽量スクリプトで効率的に作業を進めるには、 問題を単純化して簡単なアルゴリズムの短いプログラムで済むような 環境を調えることをまず考える。今回の場合はメイルを自動的に解析して データ登録をするというものだが、一般的にテキストの「解析処理」は 簡単ではない。届いた電子メイルのストリームから差出人やサブジェクト、 内容を正確に解析するのは複雑であるため、極力ストリームを見なくて よい形を考える。

今回の場合は自動受信スクリプトが差出人などの情報を得るときに ストリームではなく環境変数を参照するようにする。

メイルサーバの設定

自動受信スクリプトを手軽に行なえるように、十分な情報を環境変数の 形でスクリプトに渡す機能を備えたMTAは Postfix と qmail に限られる。 現在では多くのLinuxディストリビューションの標準MTAが Postfix になっているので、それを利用する前提で必要な追加設定を説明する。 使用しているメイルサーバのMTAが qmail の場合はすべての条件が揃っているのでとくに追加設定は必要ない。

Mail Transfer Agent の略でメイルの送受信を受け持つソフトウェアのこと。

ここでは、Postfix を利用する場合の追加設定を示す。システムへの Postfix の導入とサービスの起動は済ませているものとする。 サービスが正常に稼動していれば 自分宛のメイルが以下の操作により届くのが確認できるはずである。

echo Hello | mail -s Test-Mail $USER
from
IIMORI Hanako		Test-Mail

この例はユーザのgecos名が IIMORI Hanako である場合を示している。もし、fromコマンドで自分宛のメッセージの Subject が出てこないようであればシステムのメイル配送サービスが稼動していない。 OSのマニュアルなどを参照して正常に設定する必要がある。

パスワードファイルのアカウントの一般的な情報を記述するフィールドの値。 通常は氏名などを記録する。

Postfixの拡張アドレス設定

Postfixでは追加設定を行なうことにより、拡張メイルアドレス機能が使える。 まず、現状の値を確認する。

postconf | grep '^recipient_delimiter'

何も出力されなければ未設定である。この場合は以下のようにして、 拡張メイルアドレスの区切り文字をハイフン(-)に設定する。

sudo postconf -e recipient_delimiter=-

場合によっては、最初のgrep結果が以下の出力となるかもしれない。

recipient_delimiter = +

この場合は、既に拡張メイルアドレスの区切り文字が 「+」の状態で設定されているが、Postfixのバージョンによって対処が異なる。

続いて、拡張アドレスの機能を拡充するスクリプト dot-qmail をインストールする

wget http://www.gentei.org/~yuuji/software/dotqmail/dotqmail
chmod +x dotqmail

dotqmail は qmail に由来する便利な拡張アドレス機能を Postfix でも使えるようにするスクリプトである。詳細は http://www.gentei.org/~yuuji/software/dotqmail/ にある。 また、wget コマンドがない場合は、「ftp」(BSDの場合)、 「curl -O」で代替できる。

dotqmail は zsh スクリプトなので、zsh もインストールする。

: Debian系(apt-get)の場合
sudo apt-get -y install zsh
: Arch Linuxの場合
sudo pacman -S zsh
: FreeBSDの場合
sudo pkg install zsh

dotqmail スクリプトは zsh が /usr/local/bin にインストールされる前提になっているが、そうではない(たとえば /bin/zsh)場合は dotqmail スクリプトの1行目(shbang行)を、 インストールされた zsh のパスに書き換える。

#!/usr/local/bin/zsh -f

以下に変更する(例)。

#!/bin/zsh -f

この先の説明は dotqmail をホームディレクトリにインストールした例で 進めるが、別の場所に配置したのであればそれに置き換えて構わない。

~/.forward ファイルに dotqmail による拡張アドレス処理を記述する。 ファイルを開き、以下の内容を書き込む。既に .forward ファイルがある場合は末尾の行に追加する。

"| ~/dotqmail"

これにより Postfix が受け取ったメイルアドレスが拡張アドレス形式に なっていた場合に、拡張子部分に応じた別々の宛先(今回の場合はスクリプト) に配送されるようになる。

dot-qmail 拡張アドレス機構

Postfixの場合は前項の設定を完了すれば、qmailの場合は標準状態で 拡張アドレスに dot-qmail 機構が使えるようになっている。dot-qmail 機構は、一般ユーザ権限で任意個のメイルアドレスを自由に作成することができる。 たとえば、あるユーザのメイルアドレスが user@example.com だとする。user が ~/.qmail-foo というファイルを作成して宛先設定を書くと、 user-foo@example.com という拡張アドレスでメイルを受け取れるようになる。 また、~/.qmail-default というファイルに宛先設定を書くと user-*@example.com の * の部分を任意の単語にしたアドレス(かつ他に適合する ~/.qmail-* ファイルのないもの)でメイルを受け取れるようになる。

たとえば、ホームディレクトリに以下の3つの dot-qmail ファイルがあったとする。

.qmail-abc	.qmail-abc-default	.qmail-xyz

この場合、送信先アドレスと対応するファイルの関係は以下のようになる。

user-abc.qmail-abcの記述先に届く
user-xyz.qmail-xyzの記述先に届く
user-foo該当ファイルがないのでエラーメイルが返る
user-abc-aaa.qmail-abc-defaultの記述先に届く

dot-qmailファイルの書式

dot-qmail ファイルはホームディレクトリに作成する .qmail あるいは .qmail-単語 という名前のものであり、 1行に1つずつ配送先を定義する働きを持つ。 その書式は以下のいずれかである。

行頭文字はたらき
#コメント(無視される)# あいうえお
|プログラムの起動| ./program.sh arg
&転送アドレス&user2@example.co.jp
. または /
(スラッシュで終わらない)
mbox形式のファイル./mbox
. または /
(スラッシュで終わらる)
maildir形式のファイル./Maildir/

今回は3つめの「行頭 |」の書式を用いて、メイル自動受信による 自動採点システムの作成にあたる。

command execution via mail

メイル受信によるプログラム起動では、以下の環境変数が自動的に設定され、 起動プログラムに渡される。

環境変数値の意味
SENDERエンベロープsender
RECIPIENT実受信者のアドレス
HOST受信アドレスのドメイン部(@の後ろ)
LOCAL受信アドレスのローカル部(@の前)
EXT受信アドレスの拡張子部分
EXT2$EXTの1個目のハイフンより後ろの文字列
EXT3$EXTの2個目のハイフンより後ろの文字列
EXT4$EXTの3個目のハイフンより後ろの文字列
DEFAULTdot-qmailファイルの "default" にマッチした文字列

この性質を利用すると、電子メイルによる課題提出では 以下の環境変数が有効利用できる。

taro@example.net さんが hanako-report-01-a@example.com に送信した場合
$SENDERtaro@example.net
$EXTreport-01-a
$EXT201-a
$EXT3a

$SENDER の @ より前の部分を、提出者の学生ID、 $EXT2 の最初のハイフンより前の部分を講義の回に、 $EXT3 の値を選択肢の中から選んだ値として用いる。

自動採点プログラム簡易版の作成

ここでは極端に簡略化した採点規準に基づいたプログラムを作る。 プログラムから参照できる環境変数を利用して、以下のように採点する。

点を付ける学生ID${SENDER%%@*}
講義回${EXT2%%-*}
選んだ選択肢$EXT3
得点dを選んだら10点、その他は8点

本当の採点処理であれば得点決定部分は重い意味を持つだろうが、 今回は大胆に簡略化する。 環境変数の加工で利用している %% と % は、 後続するパターンにマッチする部分の削除で、%% は最長マッチ削除、 % は最短マッチ削除である(「パラメータ置換時の部分文字列取得」参照)。たとえば SENDER の値が user@example.com であるとき、${SENDER%%@*} は、文字列のうち @* にマッチする部分を最長で削除する。@ が1個のときは最短マッチも最長マッチも 同じ結果になるが、@が2個以上あるときは(通常ありえないが)最初の @ 以降を削除する。

${EXT2%%-*} も同様の文字列末尾パターン削除で、たとえば EXT2 の値が 02-a だったときに -* にマッチするパターンを削除するので ${EXT2%%-*} は 02 となる。

以上の結果をデータベースに登録するには score.sh を使えばよい。 ここまでの工程をシェルスクリプト化すると以下のようになる(receiver.sh)。

receiver.sh

#!/bin/sh
# EXT=report-01-a
# EXT2=01-a
# EXT3=a
PATH=/usr/local/sqlite3/bin:$PATH	# 最新版のSQLite3起動のための設定など
mydir=`dirname $0`			# このスクリプト自身の格納ディレクトリ
cd $mydir				# そこへcdしておく

nlec=${EXT2%%-*}			# 講義回
who=${SENDER%%@*}			# 送信者の学生ID

case $EXT3 in				# 得点をPTに代入する
  d)	PT=10	;;
  *)	PT=8	;;
esac

# score.sh 学生ID 講義回 得点
./score.sh $who $nlec $PT
exit 0		# メイル経由で動くプログラムは exit 0 しないとキューに溜る

続いて、ここで作成した receiver.sh を起動する設定に進む。

課題提出用メイルアドレスの作成

上記をふまえて提出用アドレスを決定し、それぞれの dot-qmail ファイルを作る。 今回は受信者のユーザ名を hanako とし、課題提出用の拡張子を report- とする。 意味は report-講義回-選択肢の一つとする。 この場合に作成すべき dot-qmail ファイルは以下のとおりである。

.qmail-report-01-default第1回の提出用
.qmail-report-02-default第2回の提出用
.qmail-report-03-default第3回の提出用
:
.qmail-report-15-default第15回の提出用

15回分の提出課題をすべて吸い込むような .qmail-report-default を作成してもよいが、その場合提出者が宛先を間違えてあらぬ講義回の 宛先に出してしまった場合などにもエラーなしで受信操作が行なわれ、 提出者が間違ったことに気づかず終わることになる。 これを好ましいとするならよいが、 間違ったらエラーを返した方がよい場合は上記のような ものにする。また上記の一覧では、選択肢部分を間違えても(たとえば report-01-x 宛に出しても)エラーなしで受信するが、 そこは間違えても正解にならないだけで、 提出したことに変わりはなく最低限の得点は与えるという判断である。

集計ディレクトリを ~/report と仮定して、必要な dot-qmail ファイルを作成する。以下に手順の例を示す。

cd			# ホームディレクトリへ
echo "| report/receiver.sh" > .qmail-report-01-default
for n in 02 03 04 05 06 07 08 09 10 11 12 13 14 15; do
 ln -s .qmail-report-01-default .qmail-report-$n-default
done

集計ディレクトリの準備

作成済ファイルの配置

先述のように ~/report/ に集計用ファイルをまとめる。 mkdir ~/report してから以下のファイルを置く。

上記2ファイルを置いてデータベースの初期化を行なう。

./score.sh -i

実際に初期テーブルができているか確認する。

sqlite3 score.sq3
.schema
CREATE TABLE students(sid PRIMARY KEY, name);
CREATE TABLE lectpts(
  sid, nlec, pts,
  UNIQUE(sid, nlec),
  FOREIGN KEY(sid) REFERENCES students(sid)
);
SELECT * FROM students;
C110123|公益太郎
C110134|飯森花子
C110138|高見台一
C110140|緑智子
hanako|葛斗花子

実際に自分の環境で確かめる場合には、自分のログイン名を ID とする行が students テーブルに含まれているか注意する。 同様の状態が確認できたら実際にレポートアドレス宛に送信してみる。

echo dummy report | mail -s Report hanako-report-01-a

宛先は自分のものに変更したうえで起動してみて、~/report/score.sq3 に得点が記録されていれば成功である。

./score.sh -s
hanako|葛斗花子|8
: (↑実際には自分のログイン名になる)

エラーの場合はシステムログ /var/log/mail.log (あるいはmaillogなど) にエラーメッセージが出るので確認する。

電子メイル自動応答データベースの要点

今回作成したシステムは採点部分は仮のものだが、 それ以外はほぼ実用に即したものである。データベースを操作するスクリプトと それを呼び出すスクリプトを合わせても100行程度のものにすぎない。 実際には異常入力への対処などで数倍の行数になるかもしれないが、 それでもコンパクトである。シェルスクリプトの記述性の高さもさることながら データの完全性はsqlite3が保証してくれる点、 データ操作記述の簡潔さはSQLがもたらしてくれる点が、 スクリプトのサイズ低減に大きく寄与している。

プログラムは短いほどバグを回避しやすくなる。 メイル自動応答システムを簡潔化している要点をまとめておく。

"eval" はシェルの内部コマンドで、引数に指定した文字列をそのまま シェルの実行文として評価するものであり、悪意を持ったユーザの入力した文字列を eval するとシステムを破壊するおそれにもつながる。 よって本稿で設計するシェルスクリプトでは一切 eval を使用していない。

練習問題: メイルアドレス・ID変換

詩作した得点集計システムでは、解答者が学生 ID と同じローカルパートを持つメイルアドレスから送信することを仮定したが、 メイルアドレスは複数持てるものである。あらかじめ自分の ID に結び付くメイルアドレスを登録しておけば、 そちらからも本人名義でレポート送信できるようになれば便利である。

score.sh は得点登録時に第1引数をそのまま ID として利用したがこれを改良し、 あらかじめ登録した文字列(送信者アドレス)に対応する ID があればそれに変換してから得点登録するように改良した score3.sh を作成せよ。なお、ID と送信者アドレスの対応表は以下のCSVファイルのように与えているものとする。

emails.csv

C110123,k.o.e.k.i.tarooooooo@codomo.example.org
C110134,flower-o_o-hanahana@easywww.example.net
C110138,h1h2h3h4hx4x.ab1225@hardbank.example.com

練習問題: 解答例

まずメイルアドレスと学生IDの対応表を作り、 続いて得点登録部分を変更するという流れで進める。

  1. emailsテーブルの作成

    外部キー制約を設定して以下のようにテーブル作成する。

    CREATE TABLE emails(
    	sid, email UNIQUE,
    	FOREIGN KEY(sid) REFERENCES students(sid));
    

    emails.csv からインポートする。

    .mode csv
    .import emails.csv emails
    

    ここでは固定的に作成する例を示したが、emails テーブルの値更新も自動化する工夫を考えてみるとよい。

  2. 得点登録の第1引数にメイルアドレスからの逆変換を挟む

    score.sh の元の得点登録部分は次のようなものであった。

    query "REPLACE INTO lectpts VALUES('$1', $2, $3);"
    

    この '$1' の部分に値の選択を入れる。 $1 は receiver.sh によって渡される、メイルアドレスのローカルパートである。 よって、emails テーブルからの参照はローカルパートでの比較を LIKE 演算子を用いて行なう。

    query "REPLACE INTO lectpts VALUES(
    	coalesce((SELECT sid FROM emails WHERE email LIKE '$1@%'),
    		 '$1'),
    	$2, $3);"
    

以上2つの修正で個人アドレスからのレポート送信が可能となる。 スクリプトの修正点がなるべく少なくなるよう、receiver.sh からの score.sh の第1引数に 送信者アドレスのローカルパートを渡す仕様のままにしたため、 メイルアドレスから学生IDへの変換に LIKE 演算子を用いた。実際には全体表記のメイルアドレスでの比較をした方がよいだろう。

yuuji@koeki-u.ac.jp