読者です 読者をやめる 読者になる 読者になる

Rich Lab. Blog

「まつらリッチ研究所」の研究の一環で、はてなブログ上にブログを公開。でもなぜか買い物カゴが……。はてブはそんな機能あったっけ?

東京メトロの列車接近情報を返すWebAPIをシェルスクリプトで叩く

鉄道 UNIX Web

既にいろいろなところで話に上っているけど、東京メトロ「オープンデータ活用コンテスト」というのをやっている。

時刻表や駅の設備といった静的な情報はもちろん、リアルタイムの列車位置なんかも公開するという大盤振る舞い。「これで役立つアプリを作ってね」ということなのだが、じゃあ俺がシェルスクリプトで何か作ってやろうじゃないかと、いうことでパイプを駆使した活用プログラムを作ってみた。

接近情報表示コマンド「メトロパイパー」

f:id:richmikan:20140921114151j:plain

一般的な意味の「パイパー」とは、
こういう人のことを言うらしい

というわけで作ってみたプログラム、その名は「メトロパイパー」。パイパーというと、本来は右の写真のような男の娘を意味するらしいのだが、ここでのパイパーとは「UNIXシェルのパイプを操る者」と「地下鉄のパイプ(=路線)を覗く者」という意味にしている。

各種情報はここ以外にも下記のサイトに置いておいたので参考にしてね。

ところで「接近情報」って?

まず接近情報とは何かだが、駅のホームへ行くとそこの電光掲示板に表示される、

【こんど】快速 東葉勝田台
【つぎ 】各停 西船橋
※ 「こんど」&「つぎ」なんていつの時代だ

とか

前々駅 -◆- 前駅 --- 当駅

というアレだ。

でも、あの情報がその駅に居なくても見られたら便利だよなー、とまえまえから思っていた。例えばこんなシーンはないだろうか。

  • 時間ギリギリにホームに着いたけど、目的の電車が来ない!遅れてるのかな?それともやっぱり出発しちゃったのかな?(あぁ、先の駅の接近情報が見られれば……)
  • 急行南栗橋行間に合わなかったなぁ……。次の東武線直通はどの駅まで来てるんだろう。
  • あ、特急ロマンスカー霞ヶ関駅あたりに来てるな。よし、じゃあそろそろ帰宅の準備して表参道駅へ行くか。

という具合に、今いる駅の、先や、もっと手前の駅の接近情報、あるいは駅にいなくてもこれから行く予定の駅の接近情報ってわかれば便利でしょ。使い方は直感的にわかるように作っているつもりなので使ってみてね。

UNIX哲学とPOSIX原理主義溢れるソースコード

このプログラムの特徴は何と言っても、中身のほとんど全て*1シェルスクリプト+UNIX標準コマンドで書かれているということだ。しかもUNIX哲学の思想を随所に採り入れ、パイプを多用している点だ。

そのいくつかを紹介しよう。

JSONテキストを行列指向に変形して捌く!

まずはソースコード「SHELL/VIEW_METROLOC_0.SH」を見てもらいたい。

######################################################################
# 列車ロケーション情報API呼び出しと、キャッシュ作成
# (キャッシュの有効期限が切れているならば)
######################################################################
:
# --- 有効キャッシュがなければ作る(タイムスタンプは有効期限日時に設定)
[ $cache_is_fresh -eq 0 ] && { curl -s $url | parsrj.sh > "$File_cache"; }
:
:

######################################################################
# API取得データ(列車ロケーション情報の取得)の加工
######################################################################

# --- 必要な項目・区間&方面のみに絞り込んだ在線位置マスターファイルを作る
cat "$File_cache" |
sed 's/^\$\[\([0-9]\{1,\}\)\]\.[^:]\{1,\}:/\1 /' |
awk '$2=="railDirection" {$2=1;print;} #
$2=="fromStation" {$2=2;print;} #
$2=="toStation" {$2=3;print;} #
$2=="trainType" {$2=4;print;} #
$2=="terminalStation"{$2=5;print;} #
$2=="trainOwner" {$2=6;print;}' |
sort -k1n,2n |
awk '{print $3}' |
awk 'NR%6!=0{printf("%s ",$0)} NR%6==0{print $0}' |
# 1:方面コード 2:発車駅コード 3:到着駅コード 4:種別コード 5:目的駅コード 6:車両所有業者コード
:

これは実際にAPIを叩いて値を解析しているコードの一部だ。

東京メトロの今回のオープンデータAPIJSON-LDという形式で情報を返してくる。そこで拙作のJSONパーサー"parsrj.sh"(→解説)で、(1)値の位置、(2)値という、各行2列で構成されるテキストに変換し、それをさらにAWKを使って(1)方面コード(上り下り的なもの)、(2)発車駅コード、(3)到着駅コード、(4)種別コード、(5)目的駅コード、(6)車両所有業者コード、という6列のデータに変換している。

こうやってJSONを一旦行列指向のフォーマットに変換してしまえば、あとはgrepAWKで絞り込んだり加工したり……と、リレーショナルデータベース的な操作が自由にできる。

JOINコマンドでマスターデータをハメ込む

「リレーショナルデータベース的な操作」と言ったからには当然JOINもできる。例えば、同じソースコード「SHELL/VIEW_METROLOC_0.SH」の下記の部分を見てもらいたい。

# --- コードを名称化し、列車存在位置から列車名を引くマスターファイルを作る
cat $Tmp-this-rw-dir-loc |
# 1:現在居る3桁駅ナンバー(3桁目は0または5で、5は中間にいることを表す) 2:種別コード
# 3:目的駅コード 4:車両所有業者コード #
sort -k2,2 |
join -1 1 -2 2 -a 2 -o 2.1,2.2,1.2,2.3,2.4 $Homedir/DATA/METRO_VOC_MST.TXT - |
sed 's/\([^. ]\{1,\}\) /\1 \1 /' |
awk '{print $1,$3,$4,$5}' |
# 1::現在居る3桁駅ナンバー(3桁目は0または5で、5は中間にいることを表す) 2:種別名
# 3:目的駅コード 4:車両所有業者コード #
sort -k3,3 |
join -1 1 -2 3 -a 2 -o 2.1,2.2,2.3,1.2,2.4 $Homedir/DATA/METRO_VOC_MST.TXT - |
sed 's/\([^. ]\{1,\}\) /\1 \1 /' |
awk '{print $1,$2,$4,$5}' |
# 1:現在居る3桁駅ナンバー(3桁目は0または5で、5は中間にいることを表す) 2:種別名
# 3:目的駅名 4:車両所有業者コード #
sort -k4,4 |
join -1 1 -2 4 -a 2 -o 2.1,2.2,2.3,2.4,1.2 $Homedir/DATA/METRO_VOC_MST.TXT - |
sed 's/\([^. ]\{1,\}\) /\1 \1 /' |
awk '{print $1,$2,$3,$5}' |
# 1:現在居る3桁駅ナンバー(3桁目は0または5で、5は中間にいることを表す) 2:種別名
# 3:目的駅名 4:車両所有業者名 |
sort -k1,1 > $Tmp-this-rw-loc

APIから送られてくる接近情報データは人間に分かりやすい名称ではなく、各種コード文字列になっている。これを人間向けにするなら、各種コードに対応する名称の記されたマスターファイルとJOINして、名称の列に置換する必要がある。

それをやっているのがこのコードだ。ここでは、種別(各停とか急行とか)、目的駅、車両所有業者を、各々コードから名称にするため、マスターファイル(METRO_VOC_MST.TXT)と3回JOINしているのがわかるだろう。

Ajaxで送るWebパーツは、JSONではなくてHTMLコードそのもの

上記はデータの加工に関する話であったが、Webインターフェースを扱う部分にも特徴が表れている。

動作デモを見てきてもらえばわかるが、駅を選択する<select>タグの中身はAjaxで書いている。大抵は、Webサーバーから<option>タグに渡す名称と値をJSONなりXMLなりで受け取り、項目の数だけdocument.createElementをforループで回すことだろう。

しかし、サーバーサイドがシェルスクリプトならではの技で、<option>タグHTMLそのものをサーバー側で生成し、<select>エレメントのinnerHTMLにゴッソリ流し込むというやり方を使っている。

まず、流し込まれるHTMLテンプレート「TEMPLATE.HTML/MAIN.HTML」を見てもらいたい。中に次のような記述の箇所がある。

  :
<td>
<select id="from_snum" name="from_snum" disabled="disabled" onchange="get_locinfo()">
<!-- FROM_SELECT_BOX -->
<option value="-">選んでください</option>
<!-- FROM_SNUM_LIST
<option value="%1">%1 : %2線-%3駅</option>
FROM_SNUM_LIST -->
<!-- FROM_SELECT_BOX -->
</select>
</td>
:

このHTMLファイルは当然サーバー上に置かれており、次のシェルスクリプト「CGI/GETSNUM_HTMLPART.AJAX.CGI」でこれを利用している。その一部を抜粋する。

# --- 部分HTMLのテンプレート抽出 -------------------------------------
cat "$Homedir/TEMPLATE.HTML/MAIN.HTML" |
sed -n '/FROM_SELECT_BOX/,/FROM_SELECT_BOX/p' > $Tmp-htmltmpl

# --- HTML本体を出力 -------------------------------------------------
cat "$Homedir/DATA/SNUM2RWSN_MST.TXT" |
# 1:駅ナンバー(sorted) 2:路線コード 3:路線名 4:路線駅コード
# 5:駅名 6:方面コード(方面駅でない場合は"-")
awk '{print substr($1,1,1),$0}' |
sort -k1f,1 -k2,2 |
awk '{print $2,$4,$6}' |
uniq |
# 1:駅ナンバー(sorted) 2:路線名 3:駅名 #
grep -i "^$rwletter" | # ←関係ある路線だけに絞り込んでいる
mojihame -lFROM_SNUM_LIST $Tmp-htmltmpl -

まず前述のHTMLのうち<option>タグの部分(FROM_SELECT_BOXというコメントで囲まれた区間)をsedで抽出している。その後、駅名マスターファイル(SNUM2RWSN_MST.TXT)の中に書いてある、(1)駅ナンバー、(2)路線名、(3)駅名のみをAWKなどで取り出し、mojihameというコマンドに流している。

ここで呼び出しているmojihameというは、やはりシェルスクリプトで書かれている自作コマンドなのだが、標準出力から与えられた行数だけ、先程抽出したテンプレートの<option>タグの行を複製するという処理を行っている。<option>タグの箇所には"%1"~"%3"というマクロ文字があるが、これらが標準入力から渡ってきた各列の文字列に置換される。これによって、

 <!-- FROM_SELECT_BOX -->
<option value="-">選んでください</option>
<option value="C01">C01 : 千代田線-代々木上原駅</option>
<option value="C02">C02 : 千代田線-代々木公園駅</option>
<option value="C03">C03 : 千代田線-明治神宮前〈原宿〉駅</option>
:
<option value="Z14">Z14 : 半蔵門線-押上〈スカイツリー前〉駅</option>
<!-- FROM_SELECT_BOX -->

というテキストが生成されるのだ。あとは、先程言ったようにこれをAjaxブラウザーに渡して、innerHTMLでごっそりハメ込んでもらえば完成。

シェルスリプトなんて非実用的でしょ?

実はこのプログラムは、そんな先入観を吹っ飛ばすべくして作った。

シェルスクリプトのポータビリティーは実は高い!

なにせこのプログラムは、curlコマンドとWebサーバー(Apache)以外は全てPOSIXの範囲で作っている。LAMPという言葉があるが、つまりMとPの部分は無し!だからLとAのある環境ならどこでも動くということだ。そしてコンパイルも不要、POSIXの範囲だからBash依存やGNU依存なども無い。さらに言うと、LとAの部分の実装は問わないので、Lの代わりはFreeBSDでも、Solarisでも構わないし、Aの代わりにnginxなどでも構わない。シェルスクリプトは高いポータビリティーを持つ、ということだ。

シェルスクリプトだって短期間開発ができる

また、「できるのはわかるけど、フレームワーク無しに一から作ってたら時間かかるだろうが」と思うかもしれないが、それも誤りだ。このコンテスト募集開始日(2014/09/12)から、本ソフトの公開日(2014/09/16)までたった4日であることに注目すべきだ。しかも実際は1日で書いている(信じないかもしれないが)。公開日が16日なのは、ソースコードの公開が利用規約に違反しないという公式回答が15日にあったので、「それなら作るか!」と作ったのが始まりだったのだ。1日、それが信じられなくても4日で作ったというこの事実が、シェルスクリプトは短期開発でも「使える」ということを証明しているだろう。

 

見直した? 見直したら、そんなシェルスクリプトでとあるアプリを開発したとうネタを収録した薄い本


コミケ4日目は、はてブで本を買おう - りっちけんきゅうじょのにっき

も、よろしくね。(ちゃっかり宣伝)

 

 

←カゴに商品が入っている時にこのボタンを押せば、レジへ移動します。

*1:curlコマンド以外