開眼シェルスクリプト2014年4月号

Pocket
LINEで送る

出典: 技術評論社SoftwareDesign

4. 開眼シェルスクリプト 第4回

4.1. はじめに

 書いている筆者本人が超地味だと公言する開眼シェルスクリプトも、
今回で第四回です。今回は(この連載にしては)派手です。ブラウザで絵を描きます。

4.1.1. 禁欲的になれとは言ってません

 端末で古いコマンドを使い、
テキストばかりいじっているのでそう思われてもしょうがないのですが、
別に昔に帰れとか、GUI見るなとか、そういうことをこの連載で言いたいわけではないのです。
むしろ、逆です。ここ数年は、データの保存や表現形式は、テキスト
(というよりも、より人間が読みやすい)データに向かっているようなので、
テキストを操作できるということは、より普遍的なスキルになりつつあるようです。

 ○○が廃れたなどと具体例を出して言うと角が立ちますので、一つ例を出します。
筆者は職業柄、画面のスクリーンショットを撮ることが多いのですが、
スクリーンショットはバイナリ形式のpng形式で保存しています。
16進数でファイルの先頭を出すと、リスト1のように見えます。
バイナリなので、catすると悲惨なことになります。

↓リスト1: png画像をodで見る。

1
2
3
ueda@uedaubuntu:~$ od -tx 201204_1.png | head -n 2
0000000 474e5089 0a1a0a0d 0d000000 52444849
0000020 df000000 ad000000 00000608 aa133000

 別にcatできなくても画像なので全く構わないわけですが、
それでも筆者は、10年後20年後には、
ビットマップ画像(脚注:ベクトル画像でなく、あくまでビットマップ画像。)
のフォーマットの最終型が次のようなテキストファイルを圧縮したものになると考えています。

↓リスト2: 近未来ビットマップ(予想)

1
2
3
4
5
y x r g b
0 0 128 128 128
0 1 255 121 121
0 2 32 128 128
...

この形式の問題はサイズがバカでかく、見た目もバカっぽいということですが、

  • ハードの進化でサイズの問題が些細なことになったらどうなる?
  • 画像の一部だけ切り取るのはpng形式とどっちが楽か?
  • 言語付属のライブラリを作ってメンテナンスする手間はどっちが楽か?
  • 形式を拡張しなければならないとき、png形式と比べてどっちが自然に拡張できるか?

などといろいろ利点、欠点を考えると、
特に嫌悪や嘲笑の対象になるような形式でもないでしょう。
実際、PDP-7上でUNIXが取った戦略は後者だったのです。

 今回は、次の有名な格言を。

  • Keep it simple, stupid.

    (KISSの原則、ケリー・ジョンソン)

4.2. 今回のお題:HTMLで表とグラフを描く

 今回のお題は、HTMLファイルをブラウザで見ることになりますが、
次の方法のいずれかで見てください。

  • httpサーバを通してHTMLファイルを見る。
  • 手元のデスクトップ機のUNIX環境でHTMLファイルを作り、手元のブラウザで見る。

後者の場合は、httpサーバは不要です。
今回は後者の場合を想定して話を進めて行きます。
今回使用する環境も、リモートのサーバではなく、
デスクトップ機です。諸元は以下のようになってます。

  • マシン:ThinkPad SL510
  • CPU, OS等:リスト3参照

↓リスト3: CPU, OS等の情報

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
ueda@uedaubuntu:~$ cat /proc/cpuinfo | grep "model name"
model name      : Celeron(R) Dual-Core CPU       T3100  @ 1.90GHz
model name      : Celeron(R) Dual-Core CPU       T3100  @ 1.90GHz
#↑補足: CPUはデュアルコアのもの1個
ueda@uedaubuntu:~$ uname -a
Linux uedaubuntu 3.0.0-14-generic #23-Ubuntu SMP Mon Nov 21 20:34:47 UTC 2011 i686 i686 i386 GNU/Linux
ueda@uedaubuntu:~$ cat /etc/lsb-release | grep DESCRIPTION
DISTRIB_DESCRIPTION="Ubuntu 11.10"
ueda@uedaubuntu:~$ firefox -v
Mozilla Firefox 9.0.1
ueda@uedaubuntu:~$ echo $LANG
ja_JP.UTF-8

4.2.1. HTMLのおさらい(あるいは初めてのHTML5)

 まず、HTMLのおさらいをします。
HTML は、テキストのどこがタイトルでどこが本文で・・・
と印をつけていく方法の一種で、単なるテキストファイルです。
それ以上のものでもそれ以下のものでもありません。
それを踏まえて、リスト4のHTMLファイルを見てみましょう。HTML5で書いています。
HTML5は、従来のHTMLの余計なものが省かれて簡素化されたので、
以前のHTMLよりは理解しやすいと思います。説明する方も簡単で助かります。

↓リスト4: HTMLのおさらい

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<!DOCTYPE html>
<html>
        <head>
                <meta charset="utf-8" />
                <title>htmlの書き方</title>
        </head>
        <body>
                あいうえお
        </body>
</html>

 リスト4の1行目は、このファイルがhtmlであるということを言っています。
シバン( #!/bin/bash )に似ています。
それ以降は、 <html></html> の間に「要素」を詰め込んでいきます。
要素というのは、

  • <hoge> から </hoge> までの塊
  • あるいは <hoge ... />

のことで、HTMLは、要素の下に要素がぶら下がって、
その下に・・・と木構造になります。
リスト1の場合は、一番外側のhtmlの下にheadとbodyがぶら下がって、
headの下にはさらにmetaとtitleがぶら下がっています。
<hoge ... /> の形式をとるときには / は不要なのですが、
筆者は / がないと閉じた感じがしないので、
かならず入れるようにしています。

 要素には、「内容」と「属性」というものがあります。
「内容」は <hoge></hoge> に挟まれた部分、
「属性」は、 <hoge a="b" c="d" ... > のように 名前="値"
を並べて書いたものです。
「内容」はその名の通り、要素が持っている情報本体で、
属性は、要素に対する味付けと考えてください。

 HTML5なので、HTML5に対応しているブラウザで見てみましょう。
上のHTMLをhoge.htmlと名前をつけてどこかに保存します。
ウェブサーバを立ち上げなくてもファイルをダブルクリックすれば見られるはずです。
環境によっては、次のように端末からfirefoxを立ち上げることもできます。
(くれぐれもリモートのマシンにssh接続している場合はやらないでください。)

ueda@uedaubuntu:~$ firefox hoge.html
_images/201204_1.png

図1:リスト1のHTMLをfirefoxで見る

見ることができましたでしょうか。

4.2.2. HTMLを出力するシェルスクリプト

 では、HTMLを出力するシェルスクリプトを作ってみましょう。
CGIスクリプトにすることもできるのですが、それは後日ということで、
とにかくHTMLファイルを作るシェルスクリプトを作ります。

 まずは、リスト5のようなシェルスクリプトから始めます。

↓リスト5: HTMLのおさらい

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
ueda@uedaubuntu:~/GIHYO$ cat html.sh
#!/bin/bash

cat << EOF > ./hoge.html
<!DOCTYPE html>
<html>
        <head><meta charset="UTF-8" /></head>
        <body>
                $(date)
        </body>
</html>
EOF

firefox ./hoge.html

リスト5で大事なのは、4~12行目の部分です。
この部分は「ヒアドキュメント」と呼ばれ、
command << EOFEOF に挟まれたテキストがそのまま
command の標準入力に入力されます。
EOFは、別の文字列でも構いません。
もしヒアドキュメントの途中でEOFと出てくる可能性があれば、
別の文字列にした方がよいでしょう。
この例では、./hoge.htmlに5~11行目の中身が溜まります。
catで見てみてください。

 もう一つ大事なのは9行目で、 $( ) でコマンドを挟むと、
コマンドの出力がヒアドキュメント中に埋め込まれます。
この例では、dateコマンドの結果がbody要素の内容になります。
$( ) の中にパイプでコマンドをずらずら連ねるとヒアドキュメント内に
HTMLとコードが混ざって汚くなるので、
以下ではヒアドキュメント内ではcatだけを使います。

 このスクリプトを実行すると、firefoxが立ち上がり、
図2のようにスクリプトを実行した時刻がブラウザの画面に表示されます。

_images/201204_2.png

図2: リスト5の実行結果

4.2.3. 表を表示

 では、何か統計情報を表にしてみましょう。
表ならば端末で見れば十分なのですが、いくつも表を並べて一度に見たり、
比較したりするにはブラウザはうってつけの道具です。
表にするのは趣味丸出しのリスト6のデータです。

↓リスト6: 通算本塁打数

1
2
3
4
5
6
ueda@uedaubuntu:~/WEB/GIHYO$ head -n 5 HOMER
順位 選手 本塁打 FROM TO 試合 打数
1 王 貞治 868 1959 1980 2831 9250
2 野村 克也 657 1954 1980 3017 10472
3 門田 博光 567 1970 1992 2571 8868
4 山本 浩二 536 1969 1986 2284 8052

 まず、このデータをそのまま表にしてみましょう。
HTMLでは表(テーブル)はリスト7のように書きます。
<tr></tr> で挟まれた部分が1行に相当、
<td></td> で挟まれた部分がテーブルの一区画(セル)に相当します。

↓リスト7: HTMLのテーブル

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<table>
        <tr>
                <td>1行1列</td>
                <td>1行2列</td>
        </tr>
        <tr>
                <td>2行1列</td>
                <td>2行2列</td>
        </tr>
</table>

これをawkで作ってみましょう。リスト8のようになります。
各フィールドを td で囲んで外側を tr で包めばよいということになります。
ただまあ、数字も名前も全部左揃えになっており、
表現力に限界があります。
なんとかしようとすると、とたんにawkの部分が膨れてしまうでしょう。
これについては、グラフのところで解決します。

 ちなみに、9行目のteeコマンドは標準入力をファイルと標準出力に二股分岐するコマンドです。
パイプの間に挟んでデバッグによく使います。

↓リスト8: 表の出力

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/bash

tmp=/tmp/$$

awk '{  print "<tr>";
        for(i=1;i<=NF;i++){print "<td>"$i"</td>"};
        print "</tr>" }' ./HOMER > $tmp-table

tee $tmp-html << EOF
<!DOCTYPE html>
<html>
        <head><meta charset="utf-8" /></head>
        <body>
                <h1 style="font-size:18px">通算本塁打</h1>
                <table border="1" cellspacing="0">
$(cat $tmp-table)
                </table>
        </body>
</html>
EOF

firefox $tmp-html
rm -f $tmp-*
_images/201204_3.png

図3:表の出力

 次のグラフ描画の際に使うので、cssについて簡単に説明します。
リスト8のh1の属性: style=”font-size:18px” は、
h1の内容がブラウザに描かれるときのフォントの大きさを指定しています。
font-size:18px の部分はcssと呼ばれるもので、
属性1:値1;属性2:値2;... というように並べていくと、
ブラウザへの出力方法を細かく指定できます。
どんな属性があるかは、ウェブ上に様々な情報があるのでそちらに譲ります。

4.2.4. グラフを描く

 では、次にリスト8の本塁打数をグラフにしてみましょう。
絵を描くわけですが、ここではSVG(scalable vector graphics)
というものをHTMLに埋め込んで使います。
まずは理屈抜きで、HTMLの例をリスト9に、ブラウザで見たものを図4に示します。

↓リスト9: svgで描画するHTML

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
ueda@uedaubuntu:~/GIHYO$ cat svg.html
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8" /></head>
<!--注意:インデントは正しく!-->
<body>
<svg>
  <text x="10" y="36" style="font-size:16px">USP</text>
  <rect x="50" y="20" width="60" height="20"
    fill="white" stroke="black" />
  <text x="110" y="36" style="text-anchor:end">00</text>
</svg>
</body>
</html>
_images/201204_4.png

図4:表の出力

  <svg></svg> の間に、rectやらtextやらがいますが、
要は図形一つ一つを指定していくとブラウザに
直接図形を描き出してくれるということです。
こんな便利な機能を使わない手はありません。
ただ、図形の部分のHTMLをawkで出力しようとすると
ややこしいコードになってしまうという問題があります。

 ここでは、mojihameという便利コマンドを使うことにします。
おそらく聞いたことが無いコマンドだと思いますが、
USP研究所のコマンドの一部がスクリプト言語で公開されているので、
ネットからダウンロードできます。

からたどっていくとOpen usp tukubai
という名前のコマンドセットがダウンロードできるので、
その中のmojihameというコマンドを使います。
設定方法はサイトで確認できますが、
単なるpythonのスクリプトなので、スクリプトをダウンロードして
python mojihame と打てば実行できます。

 mojihameはリスト10のように使います。
temp ファイルの%1、%2、・・・というのは、
ここをデータファイルの第1、第2フィールドで置き換えるという意味で、
3行目、5行目の「AAA」はこの間を
データファイルのレコードの数だけ繰り返し出力しろという意味のマークです。
mojihameで -lAAA とマークをオプションで指定して、
tempとdataを入力すると、レコードがテンプレートに嵌って出力されます。

↓リスト10: mojihameの使い方

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
ueda@uedaubuntu:~$ cat temp
長者番付(秘)
AAA
%1位 %2さん 納税額%3円
AAA
ueda@uedaubuntu:~$ cat data
1 松浦 12
2 濱田 8
3 上田 -5
4 法林 -110
ueda@uedaubuntu:~$ mojihame -lAAA temp data
長者番付(秘)
1位 松浦さん 納税額12円
2位 濱田さん 納税額8円
3位 上田さん 納税額-5円
4位 法林さん 納税額-110円

 では、mojihame+svgで本塁打数を横向きの棒グラフで書いてみます。
図4のUSPのところに選手名、00のところに本塁打数を書きます。
また、本塁打数に比例させて四角の幅を変えます。

 やることは、HTMLでmojihame用のテンプレートを書くことと、
mojihameに食わせるデータを準備することです。
リスト11に、最終的なスクリプトを示します。
図5はブラウザに表示される絵です。
描画なので座標の指定がややこしいですが、
テンプレートをいじりながら必要なフィールドを泥縄式に足していっただけなので、
頭はそんなに使ってません。

↓リスト11: グラフを描くスクリプト

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/bin/bash -vx
tmp=/tmp/$$

#1:順位 2:選手 3:本塁打 4:FROM 5:TO 6:試合 7:打数
#ヘッダを削る
tail -n +2 ./HOMER      |
#上位10傑
head                    |
awk '{wid=$3/2;print $2,$3,NR*24,NR*24+16,wid+95,wid}' > $tmp-data
#1:選手名 2:本塁打数 3:グラフ左上y座標 4:字左下y座標
#5:本塁打数文字右端位置 6:グラフ幅

#テンプレートを準備
cat << EOF > $tmp-template
<!DOCTYPE html>
<html>
  <head><meta charset="UTF-8" /></head>
  <body>
    <svg style="height:500px;width:800px;font-size:16px">
<!-- RECORDS -->
      <text x="10" y="%4">%1</text>
      <rect x="100" y="%3" width="%6" height="20"
        fill="white" stroke="black" />
      <text x="%5" y="%4" style="text-anchor:end">%2</text>
<!-- RECORDS -->
</svg>
</body>
</html>
EOF

#レコードをテンプレートに流し込む
mojihame -lRECORDS $tmp-template $tmp-data > $tmp-html
#表示
firefox $tmp-html

rm -f $tmp-*
exit 0
_images/201204_5.png

図5:リスト10の結果

 さらに派手にしたものを図6に示します。
これはコードが長い(それでも72行しかない)ので紙面には載せられませんが、
https://github.com/ryuichiueda/SoftwareDesign にアップロードします。

_images/201204_6.png

図6:さらにお絵描きを凝ったもの

4.3. 終わりに

 今回は、bashを使ってHTMLファイルを作成しました。
意外にも親和性が高いということが示せたと思います。
HTML5やUTF-8などの普及で、
昔ほど難しいことをやらなくてもできることが増えています。
今後も「技術的に難しくても本質的に難しく無いもの」
はどんどん簡単になっていくでしょう。
シェルスクリプトの出番も増えるかもしれません。

 もう一つ新しい話題として、今回はmojihameというコマンドを使いました。
ほとんど反則技ですが(脚注:弊社で初めて見たときは本当に反則だと思いました。)、
便利になるコマンドはどんな言語でもよいから作って使えばよいという、
これもシェルスクリプトらしい特長になっていると思います。

Pocket
LINEで送る