CGIは、標準入出力と環境変数を用いて動的コンテンツを生成する仕組みなので、これらを扱うことができるプログラムだったら記述言語は問いません。CGIと言えば「CGI/Perl」の組み合わせだけが突出して有名ですが、CGI/Cだって世の中にはたくさんあります。 さらに極端な事を言えば、CGI/awkだってCGI/shだって不可能ではありません。
ただし、CGI/shといったものは少なくとも私の知る限りでは、現在推奨はされていません。 Webインタフェイスとして動作するためのセキュリティを高めることに対する煩雑さであったり、大量のリクエストを効率的に捌くための本質的な性能であったり様々でしょうが、わざわざシェルスクリプトを引っ張り出してこなくても、Perl や Ruby、Python と言った気の利いた高級言語が使える環境が整っていることがほとんどでしょう。
じゃあ何故 CGI 用途にシェルスクリプトを使うかっていうと、限定された趣味の世界で、趣味目的で書く、というところが現実的なんじゃないかなって思っています。また、「(高級言語を使わなくても)シェルスクリプトだっていろいろ出来るよ!」という気持ちもあるかもしれません。(PQI Air Card のようなめちゃくちゃ小さい Linux システムにもシェルは搭載されているので、CGI/sh で簡単なウィキシステムを書いて遊んでみたりしました。)
bash.CGI
CGI として動作させるために重要なのが、リクエストの解析です。必要なものは環境変数に詰まっていますし、POST送信の内容は標準入力を読めば自分で何とでも出来ますが、シンプルかつしっかり動くbash.CGIというコードのサンプルがあるので、それをモジュールとして使いたいと思います。このコードは、CGI/Perl における CGI.pm みたいなもので、GETによるURLに付随してくるパラメータやPOST送信されたデータをシェル変数に置き換えてくれるので、いろいろ捗ります。
cgi_get_POST_vars()
、cgi_decodevar()
、cgi_getvars()
の3つの関数定義と、呼び出しの大元である cgi_getvars BOTH ALL
コマンドを貼っつけたスクリプトファイルを bashcgi.sh として保存しておきます。
bootstrap
最近のナウいユーザインタフェイスをちゃっちゃか実現するために Twitter の bootstrap を使っています。今回はサーバーのルートに /css や /js といったディレクトリが展開されるよう配置しています。
shellchat
さていよいよ本題。シェルスクリプトによる CGI チャットのソースコードは次のようになりました。
#!/bin/bash
. bashcgi.sh
title='shellchat!'
logfile='./log.txt'
linenb='15'
if [ -n "$nick" -a -n "$msg" ]; then
msg=$(echo "$msg" | sed 's/</\</g' | sed 's/\(http:\/\/[^ ]*\)/<a href=\1 target=_blank>\1<\/a>/g')
nick=$(echo "$nick" | sed 's/</\</g')
timestamp=$(date +"%m/%d %H:%M:%S")
echo "<code>$nick</code> <span>$msg</span> - <span class=small>$timestamp</span><br />" >> "$logfile"
cat<<EOF
Content-Type: text/html
<meta http-equiv="Refresh" content="0; URL=$SCRIPT_NAME?nick=$nick" />
EOF
else
cat<<EOF
Content-Type: text/html
Pragma: no-cache
Cache-Control: no-cache
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="Refresh" content="40; URL=$SCRIPT_NAME?nick=$nick" />
<link rel="stylesheet" href="/css/bootstrap.min.css">
<script src="/js/bootstrap.min.js"></script>
<title>$title</title>
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-md-2"></div>
<div class="col-md-8">
<h1>$title</h1>
<div class="well">
EOF
[ -f "$logfile" ] && tail -n $linenb "$logfile"
cat<<EOF
</div>
</div>
<div class="col-md-2"></div>
</div>
<div class="row">
<div class="col-md-2"></div>
<form class="form-inline" action="$SCRIPT_NAME" method="post" enctype="application/x-www-form-urlencoded">
<div class="col-md-8">
<p class="text-center">
<input class="form-control" type="text" name="nick" size="5" placeholder="Name" value="$nick" />
<input class="form-control" type="text" name="msg" size="59" placeholder="Input message here." />
<button type="submit" class="btn btn-default">
<span class="glyphicon glyphicon-comment"></span>
</button>
</p>
</div>
</form>
<div class="col-md-2"></div>
</div>
<div class="row">
<div class="col-md-2"></div>
<div class="col-md-8">
<hr />
<p class="text-right">
shellchat by <a href="https://dyama.org/" target="_blank">dyama.org</a>
</p>
</div>
<div class="col-md-2"></div>
</div>
</div>
</div>
</body>
</html>
EOF
fi
exit 0
全体的にどべえーっと長ったらしい HTML タグがありますが、ほとんどがデザイン部です。実際に処理しているのは数行しかありません。 冒頭で bashcgi.sh を読み込んで、シェル変数に POST 送信の内容を詰め込んでいます。変数 nick と msg があれば、ログに追記した後、ユーザにはページをリフレッシュするよう META タグを返しています。また、変数がない場合はそのままログを表示しています。
セキュリティ的にも性能としても、何もやっていない分、いろいろと問題があるのですが、一応ちゃんと動いています。
実際に動かすと、上記のように表示されます。