コード型ログ(5) Bashで自分のディレクトリを知る

シェルスクリプトで自分のディレクトリを知りたいことがあります。

. ./functions.sh

例えば、上のように他のシェルスクリプトを読み込みたいときに相対パスを使うのはよくないときがあります。なぜならば、./の場所はシェルスクリプトを実行するカレントディレクトリであって、シェルスクリプトのファイルがある場所ではないからです。シェルスクリプトは、どのディレクトリから実行しても動くようにしたいものです。

シェルスクリプトのファイルが存在するディレクトリを知るには以下のようにします。

MYNAME="${BASH_SOURCE:-$0}"
MYDIR=$(cd -P -- $(dirname -- "${MYNAME}"); pwd)

${MYNAME}には今のファイルの相対パスが入っています。

${BASH_SOURCE:-$0}となっているのは、$0にはBash以外では今のファイルの名が入るのですが、Bashだとsourceもしくは.でファイルを呼んだ実行ファイルの名前が入るからです。Bashには今のファイル名を表す変数BASH_SOURCEというのが定義されているので、BASH_SOURCEが定義されている場合にはそれを使い、BASH_SOURCEが定義されていない、つまりBash以外のシェルの場合は$0を使います。

"${A:-a}"というのは変数Aが定義されているときは${A}を返し、そうでないときはaを返すという意味です。なお、:-のセミコロンは省略して${A-a}でも可能です。*1

${MYDIR}には今のファイルのディレクトリの絶対パスが入っています。

dirname -- "${MYNAME}ディレクトリ名を取得します。--はファイル名が-で始まっているような場合にコマンドラインオプションとしてみなされないようにするためです。慣例として--以降の引数はオプションとしてみなされません。

しかし、このままでは相対パスかもしれません。なので、絶対パスが欲しいときには、$(cd -P -- ${RELATIVE_DIR}; pwd)として、絶対パスに変化します。-P${RELATIVE_DIR}シンボリックリンクが含まれていたときにそれを解くオプションです。

. ${MYDIR}/functions.sh

とすれば、どこから実行ファイルを呼んでもfunctions.shを見つけることができます。


追記

上では、実行ファイルがシンボリックリンクとなっている場合は、実体のあるディレクトリは分からない。その場合には、

MYNAME=$(readlink -f "${BASH_SOURCE:-$0}")

とする。

*1:ややこしいので個人的には"${A:-a}"で統一してほしいです。

構造化されたテストスクリプト群の実行ツール

テストコードってどんどん増えていく。だらだらスクリプトを書くのは簡単だが、すぐに収拾が付かなくなり、経済的に耐えられないレベルで混乱してくる。何のテストをしているのかももちろんわからないが、動かし方も分からないし、どうなったらpassなのかも分からないような、テストスイートが乱立して、もう全部すてればと思ってしまうほどになる。

テストの自動化を整備するなら、アーキテクチャが必要だ。ユニットテストは、JUnitやSpockやBatsなどの便利なフレームワークがあるので、散らかりっぷりが抑えられる。フレームワークの良さは、それに従えば勝手に整理されるということだ。

システムテストになってくると、システムによってテストも大きく異なってくるので、テスティングフレームワークと呼べるほどに親切なフレームワークを作るのは難しい。だが、既存のテスティングフレームワークの思想は参考にできることが多い:

  • テストケースで共通化できる処理は、Before / After でまとめる;
  • テストケースは独立である;
  • テストは繰り返し実行可能である。

乱立して増え続けるテスト用のスクリプト群もこれを守るように構造化されれば、経済的なメリットは大きいだろう。

そこで、構造化されたテストスクリプト群を実行するツールを作っている。構造化されていることを期待して実行するので、構造化されていなければテストを実行できず、構造化された状態が保たれるはず――という寸法だ。

test
├── after
│   └── run_destroy.sh
├── before
│   └── run_deploy.sh
├── testsuite1
│   ├── setup_db.sh
│   ├── test1.sh
│   └── test2.sh
└── testsuite2
    ├── post-run.sh
    ├── pre-run.sh
    ├── run1.sh
    └── run2.sh

上のようなディレクトリ構造のテストがあったときに、テストを実行すると以下のようにディレクトリ構造にしたがってスクリプトが実行される。

1..14
ok 1 before/run_deploy.sh # 0.0 sec
ok 2 testsuite1/setup_db.sh # 0.0 sec
ok 3 testsuite1/test1.sh # 0.0 sec
ok 4 testsuite1/setup_db.sh # 0.0 sec
ok 5 testsuite1/test2.sh # 0.0 sec
ok 6 after/run_destroy.sh # 0.0 sec
ok 7 before/run_deploy.sh # 0.0 sec
ok 8 testsuite2/pre-run.sh # 0.0 sec
ok 9 testsuite2/run1.sh # 0.0 sec
ok 10 testsuite2/post-run.sh # 0.0 sec
ok 11 testsuite2/pre-run.sh # 0.0 sec
ok 12 testsuite2/run2.sh # 0.0 sec
ok 13 testsuite2/post-run.sh # 0.0 sec
ok 14 after/run_destroy.sh # 0.0 sec

run, testの前後に同じ階層のbefore/after, pre/post, setup/teardownが実行されていく。

JUnitとちがって、before/afterも1ケースとしている。これはスクリプトファイルを独立して扱いたいという意図である。

それぞれのスクリプトは、シェルスクリプトをベタ書きしても良いし、そこから Ansible や他のテスティングフレームワークなどの別のツールを呼んだりする。

Jenkinsとの組み合わせを意図してTAPで結果を出力するようにしている。おそらく世間では Jenkins のビルドプロジェクトの設定を工夫して、今回したいようなことを実現しているのだろうが、Jenkins と業務が密結合になってしまう。また、Jenkinsを駆使するならば、シェルスクリプトベタ書きと変わらない。間にこういうのをかませたら便利かと思っている。

ある程度できたら公開したい。単純なものは逆に難しかったりする。本当に、JUnit は良く出来ている。


追記 2016-07-24

できた。

TT-Runner: テストスクリプトのディレクトリ構造フレームワーク - 超ウィザード級ハッカーのたのしみ

ncatで遊んでみる

ncatという超便利コマンドを恥ずかしながらいままで知らなかった。HTTPプロトコルを学ぶには最適なおもちゃだ。

www.example.com の 80番ポートに、 / を GET するという HTTP リクエストを投げてみる。

fjk@x240:~$ ncat www.example.com 80 << END
GET / HTTP/1.1
Host: www.example.com

END

すると、以下のような HTTP レスポンスが返ってくる。200番のステータスコードが帰ってきているので、リクエストはうまいこといったようだ。いろいろヘッダーフィールドについているが、こう見ると HTTP プロトコルはテキストを送って返すだけという非常に単純なプロトコルなことが分かる。

HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: max-age=604800
Content-Type: text/html
Date: Tue, 12 Jul 2016 14:47:58 GMT
Etag: "359670651"
Expires: Tue, 19 Jul 2016 14:47:58 GMT
Last-Modified: Fri, 09 Aug 2013 23:54:35 GMT
Server: ECS (rhv/818F)
Vary: Accept-Encoding
X-Cache: HIT
x-ec-custom-error: 1
Content-Length: 1270

<!doctype html>
<html>
<head>
    <title>Example Domain</title>
以下略

www.google.com に GET を投げると 302 が帰ってきた。

fjk@x240:~$ ncat www.google.com 80 << END
GET / HTTP/1.1
Host: www.google.com

END

HTTP/1.1 302 Found
Cache-Control: private
Content-Type: text/html; charset=UTF-8
Location: http://www.google.co.jp/?gfe_rd=cr&ei=-AKFV4uqMq7U8AeQ67-IBA
Content-Length: 261
Date: Tue, 12 Jul 2016 14:47:20 GMT

<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>302 Moved</TITLE></HEAD><BODY>
<H1>302 Moved</H1>
The document has moved
<A HREF="http://www.google.co.jp/?gfe_rd=cr&amp;ei=-AKFV4uqMq7U8AeQ67-IBA">here</A>.
</BODY></HTML>

http://www.google.co.jp/?gfe_rd=cr&ei=-AKFV4uqMq7U8AeQ67-IBA に移動しているらしい。この文字列の意味は ncat では分からない。

ncatはソケットにリクエストを投げるだけでなくて、リクエストを待つことができる。

fjk@x240:~$ sudo ncat -l -p 8000 << END
HTTP/1.1 200 OK

<HTML><HEAD></HEAD><BODY><H1>HELLO WORLD</H1></BODY></HTML>
END

としたら、8000番ポートに接続されたときに、 HTTPレスポンスを返す。ブラウザで http://localhost:8000/ にアクセスしたら

f:id:fjkz:20160713000501p:plain

ちゃんとWebサーバーっぽい動きをしている。

リクエスト内容は標準出力に吐かれる。

GET / HTTP/1.1
Host: localhost:8000
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch
Accept-Language: ja,en-US;q=0.8,en;q=0.6

Google Chrome は HTTP 1.1 でリクエストを投げて、Chrome のバージョンもリクエストに乗せていることが分かる。

試してみると、HTTP はテキストベースで、すごい単純なプロトコルであることが見える。もっと良いプロトコルがあるに違いないが、現実的にはこういう単純で原始的なプロトコルを組み合わせると、今の巨大なインターネットになると思うと、なんとも感慨深いものがある。

テスト駆動開発で品質が上がる証拠

テスト駆動開発(TDD)で品質が上がる証拠を得た。

Realizing quality improvement through test driven development: result and experiences of four industrial teams*1

性質が異なる4つの製品の開発プロジェクトで TDD を行って、TDD でない類似の開発プロジェクトとの比較をした。4つのプロジェクトはデバイスドライバ・OS・Webアプリケーション・PCアプリケーションと全く異なる種類のものである。

f:id:fjkz:20160708213157p:plain

欠陥密度は40%から90%減少し、一方で実装時間は15%から35%増加するという結果が得られた。データだけ見ると、TDDの導入は投資としてはお得だと言える。

インターネットの物理学

服部の『Amazonランキングの謎を解く: 確率的な順位付けが教える売上の構造 (DOJIN選書)』を読んで、なかなか興味深かった。(興味深かったけれども、本書は分かる人にしか分からなくて、分かる人は原著論文が読めるレベルという一体誰向けに書いているのか不明な非常に読みづらい本である。私も5割ぐらいしか分からなかった。余談や脱線も多くて、構成も稚拙だ。編集者はもっと頑張って欲しい。)

この一年以内に読んだ本で最も面白かったのは、バラバシの『新ネットワーク思考―世界のしくみを読み解く』という本であった。

どちらも、インターネット時代に現れた現象を古典力学の側面から切り取とろう試みをしている。服部はAmazonのランキングを、バラバシはWebベージごとのリンクを対象とした。

物理学は名前の通り、光だとか水だとか物質的な現象を対象とした学問であるが、ネタが尽きてきたということもあるのかそれ以外の現象にも手が広がっている。例えば、経済現象を物理学的な方法論で分析しようという試みは経済物理学と呼ばれたりする。

インターネットの普及とともに、インターネットがなければ見られない現象を調べようという試みが行われるようになった。インターネットはデータが取れるから、物理学の手法を試すにはうってつけなのだ。データがないことやもう調べつくされたことが、物理学のボトルネックなので、データも簡易に集められてかつ未開拓な領域に物理学者が押し寄せるのだ。インターネットはCERNで物理学ための道具として発明されたが、インターネットが逆に物理学の対象になっている事実が面白い。

個人的に、ソフトウェアも物理学の対象にできると予想を立てている。ソースコードとその成長の過程を公開するのが当然となっている現在、ソフトウェアも科学の対象にできそうだと思っている。ネットワーク理論だとか統計力学理論が使えそうで、ソフトウェアの成長は熱力学過程とのアナロジーで議論できそうだ。調べたいが、メトリクス収集ツールを作るところからはじめなければならないことと、そこまでやるには趣味ではきついことから、できていない。そして、それが私以外の人にとって何の意味があるのかも分からないので、スポンサーも募りようがない。

リコメンデーションのための相関係数2

ユーザーに対して類似のアイテムを推薦するアルゴリズムのアプローチには、

  1. ユーザーの相関をとるアプローチ、
  2. アイテムの相関をとるアプローチ

がある。今回は2のアイテムの相関をとるアプローチについて。

item A item B item C item D
user 1 2 0 2 1
user 2 3 0 1 3
user 3 2 1 3 1
user 4 2 2 4 X

前回と同様に、4人のユーザーと4個のアイテムがあったとして、それぞれのユーザーが各アイテムに上の表のような評価を与えているとする。user4のitem Dに対する評価は不明で、今これを推定したいとする。

アイテムごとにユーザーの評価のベクトルが与えられる。ここでは、user 1, 2, 3の評価のベクトルを取る。item A では (2, 3, 2), item B では (0, 0, 1), item C では (2, 1, 3), item D では (1, 3, 1) となる。

これらのベクトルの類似度をもって、アイテムの類似度とすることができる。2つのベクトルの類似度は、間の角度によって求められる。便利なので間の角度のコサイン値を類似の尺度とする。これは、コサイン類似度と呼ばれる。[0, 1]の値を取り、1 に近いほど類似度が多きい。

f:id:fjkz:20160701192653p:plain

item a, b のベクトル →p_a, →p_b のコサイン類似度は以下で計算できる。

f:id:fjkz:20160701211219p:plain

ドットは内積、||はベクトルの長さの意味です。

計算してみると、

C(A, D) = 0.95
C(B, D) = 0.30
C(C, D) = 0.64

のとなるので、item D は item A と似ていると言える。

user u の item a に対する評価の予測値は以下で計算する。

f:id:fjkz:20160701212359p:plain

ここで、N は類似度が高いアイテムの集合である。

N = {A} とすると、user D の item 4 に対する評価の予測値は、2となる。

リコメンデーションのための相関係数1

ユーザーに対して類似のアイテムを推薦すること、いわゆるリコメンデーションを機械にさせるのが、浸透している。リコメンデーションのアルゴリズムというのは興味深い。

item A item B item C item D
user 1 2 0 2 1
user 2 3 0 1 3
user 3 2 1 3 1
user 4 2 2 4 X

4人のユーザーと4個のアイテムがあったとして、それぞれのユーザーが各アイテムに上の表のような評価を与えているとする。user4のitem Dに対する評価は不明で、今これを推定したいとする。

アプローチとしては2つあって、

  1. ユーザーの相関をとるアプローチ、
  2. アイテムの相関をとるアプローチ

がある。

今回は1について。

ユーザーごとの相関を計算には、ビアソンの相関係数がもっとも基本的な形だ。

user a, item b との相関係数は以下で計算できる。

f:id:fjkz:20160630210116j:plain

ここで、 P はアイテムの集合であり、 r_i, j は user i のitem j に対する評価であり、 \bar{r_i} は user i のアイテムの評価の平均値である。

相関係数なので[-1, 1]の範囲を取り、1に近いほど類似度が高いことを示している。

今回は user 4 に似たユーザーを知りたいので、 user 4 と user 1, 2, 3 の相関係数を求める。 user 4 には item D の値がないので、 P = {A, B, C} です。

計算してみると、

C(A, D) = 0.50
C(B, D) = - 0.19
C(C, D) = 0.87

となり、user A は user D と高い相関があることが分かる。

user a の item p に対する評価の予測値は、以下の式から求められる。

f:id:fjkz:20160630223945j:plain

ここで、Nは最近傍ユーザーの集合である。

user C を user D の再近傍ユーザーとすると、 user D の item 4 に対する評価の予測値は、

r_pred(D, 4) = 2.67 + 0.87 * (1 - 2.0) / 0.87 = 1.67

となる。