bashを読もう!『オープンソースアプリケーションのアーキテクチャ』3.1〜3.3

2014.08.31

こんにちは、虎塚です。

8月31日(日)に「オープンソースアプリケーションのアーキテクチャ読書会(1)」という勉強会に参加してきました。

とても面白い本だったので、3章の3.1〜3.2の内容をレポートしたいと思います。

はじめに

読書会の開催経緯

主催者の方がある経緯でSQLiteのソースコードを読もうとしたところ、想定したより巨大だったので、先にBarkeley DBを読むことにしたそうです。

Barkeley DBの資料を探す中で、この本『Architecture of Open Source Applications』に辿り着き、せっかくなので他にも面白そうな章を読んでみようということで、今回の読書会が企画されたとのことです。

読書会の進行

20人ほどの参加者を席順に従って2人ずつのペアに分け、それぞれに章の一部を割り当てました。30分ほどかけて読みながら内容をまとめ、その後で、順番に発表して行くという形式でした。実際にはペースを見ながら調整が行われ、読み込みと発表準備は15分延長されて、45分になりました。担当範囲は各ペア3〜4ページでした。

初回なので、ためしに上記の方法でということで行われましたが、とても上手くいったのではと感じました。

というわけで、内容をレポートします。

第3章 The Bourne-Again Shell

Chet Rameyさんが書かれた章です。Rameyさんは、bashの開発者兼メンテナをされている方だそうです。

3.1 導入

シェルとは何かという紹介でした。

vi風にiで挿入モードにしなければ入力ができないシェルがある、という話で盛り上がりました。

Bash

Bashの名前の由来は、Bourne-Again SHellの頭文字を取ったもの。、Bourne-Again SHellとは、「Stephen Bourneさんが作った元祖Bourne Shellに基づいて一から作った」という意味と、「生まれ変わらせた(born again)」という意味をかけているそうです。bashの作者は、Brian Foxさんです。(記述を修正しました。ご指摘ありがとうございます)

bashは移植性が高く、ビルドするにはPOSIX環境があるだけでよい、という記述のくだりで、MicrosoftのServices for Unix(SFU)でもよい、とありました。しかし、参加者から、SFUはWindows 8で廃止されたとの情報が寄せられました。SFUとは、Microsoftの公式Cygwinのようなものだそうです(私は使ったことがありません)。

3.2 構文単位およびプリミティブ

プリミティブ

bashのトークンは次の3種類です。

  • 予約語 (if, whileなど)
  • 単語
  • 演算子

変数およびパラメータ

おそらく次のことだと思われます。

  • $@(引数ぜんぶ)
  • $?(直前のexitの値)

パラメータは代入によって与えられます。パラメータが指定されても、何も文字を与えなければ、与えられません。つまり、「a=」のような指定は、空文字とみなされるそうです。ここで、unsetとみなされる、とありましたが、空文字との区別は分かりませんでした。(分かる方がいらっしゃいましたら、お教えください)

ドル記号の単語による参照と書き換え、という話題がありました。次のような動作のことでしょう。

$ c=Hello
$ d="$c World"
$ echo $d

ローカル変数とグルーバル変数について。

LANG=C foo

のようにしてfooコマンドを実行すると、ロケールの一時変更がその後に残りません。コマンドローカルとでもいえばよいでしょうか。

ちなみに、グローバル変数が標準なので、上記は後ろにコマンドをつけるとローカル変数になり、つけないとグローバル変数になります。

また、変数に対して、明示的に型を指定することができます。配列や整数を定義できます。

declare -a 配列
declare -i 整数

型を指定した場合と、しなかった場合の動作の違いを見てみます。

$ declare -i a
$ a=1+1
$ b=1+1
$ echo $a
2
$ echo $b
1+1

上記では、echo $((b)) とすると、2が出力されます。$((1+1)) でも構いません。なお、配列の入れ子は作ることができません。

グローバルスコープ -> 一時スコープ -> ローカルスコープといったように、単位ごとにスコープが連結されているという話題も出てきます。

シェルプログラミング言語

bashはシェルプログラミング言語として十分な機能を持っている、という解説でした。

3.3 入力の処理

この節に入るにあたって、「bashのソースコードを見た方が理解が深まる」という注釈が参加者からありました。興味のある方は見てみてください。

bashの入力読み込み
対話モード: ターミナル
非対話モード: スクリプトファイル

文字を受け取り、行単位に分け、行をパーサに渡してコマンドに変換します。

文字の受け取りには、readlineライブラリが使われています。

  • コマンドラインの編集、入力内容の保存、過去コマンドの呼び出し、履歴展開
  • キーシーケンスをreadlineコマンドにバインド
  • コマンドを使ったユーザ定義マクロが使える
Readlineの構造
読み込み/送出/実行/再表示
文字読み込み: read あるいは、マクロ
読み込んだ文字がキーマップのインデックスになる(8bitの1文字)
例)beginning-of-line: 行の開始位置に移動する
例)self-insert: 文字をバッファに入れる
あるキーシーケンスの一部を他のコマンドにバインドする、という機能もある(具体例を探し中)

readlineの管理対象はCのcharだけです。マルチバイト文字をサポートするロケールでは、readlineが自動的に文字全体を読み込んでバッファに追加します。

キーシーケンスが編集コマンドに解決されたら、ターミナルを更新します。この時、次の3つのことを気にする必要があります。

1.画面に表示されている文字バッファの現在の状態 2.表示バッファの更新後の内容 3.表示されている文字

マルチバイト文字を扱う場合、1,2と3のバイト数が正確に一致するとは限りません。そこでreadlineは、1と2の差分を算出し、最適な表示方法を決定します。

アプリケーション側からのReadlineの拡張

readlineライブラリの動きをカスタマイズする方法が、2つ紹介されています。

readlineの引数
実際、bashではそのようにして拡張されたコマンドが数十個もあります
フック関数へのポインタに、既知の名前とインタフェースを使う
アプリがreadlineの前に割り込むことができます

非インタラクティブな入力処理

readlineをシェルが使わない場合は、入力の受け取りにstdio等を使うことになりますが、非インタラクティブなモードであれば、bashのバッファ入力パッケージを使った方がよいとのことです。

マルチバイト文字対応

bashでは、ロケールを見てマルチバイトを使える環境かどうかを判断して、そうであった場合は入力をバイトバッファとして格納します。

readlineライブラリは、次のようなマルチバイト文字の扱い方を知っています。

  • ある文字が画面でどれくらい場所をとるか
  • バッファからの取り出しバイト数
  • 前後移動する時にどこまで動くか

おわりに

というわけで、今日のレポートはここまでです。

じつは、今日の読書会では3章と4章の途中まで読んだのですが、ヘビーな内容だったこともあり、レポートは私の理解が追いついた3章の3.1〜3.3までです。続きを今度書くつもりです。

それでは、また。