by shigemk2

当面は技術的なことしか書かない

AOSA 3 The Bourne-Again Shell その1 #read_aosa

AOSA 3 The Bourne-Again Shell その1 #read_aosa

プリミティブ

bash 3つのトークン

  • 予約語(while)
  • 演算子(=)
  • 単語(それ以外)

変数およびパラメータ

  • 変数の値は文字列
  • 何も代入されていない変数はunset扱い
  • $で始まる単語は変数あるいはパラメータへの参照を意味する
  • 変数は基本グロオーバルで、コマンドのあとに代入文がきたやつはローカル

  • 整数値(not 静数値) Linuxの使い方 - シェルスクリプトの作り方(7/8) declareをやると型を指定できる

  • 配列の中に配列が入ることはない

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

  • echoやcdみたく引数をつけたりリダイレクトしたりできる
  • 入力や出力をシェルのユーザーが制御できるのがリダイレクト

  • 予約語やらパイプラインでより複雑なコマンドを実行できる

  • 標準入出力エラー出力を別のプロセスを送る機能がある

さらなる注意

  • Bashで扱えるデータ構造はわずかである(攻撃力はさほどでもないみたいなあれ)
  • 配列
  • ツリー
  • 片方向連結リスト
  • 双方向連結リスト
  • ハッシュテーブル

スティーブン・ボーン - Wikipedia

AOSA 3 The Bourne-Again Shell その2 #read_aosa

入力の処理

  • 入力処理は行単位で行われる
  • コマンドラインの編集はreadlineライブラリを使って行われる
  • キーシーケンス(カーソル移動や行削除などのキーバインド)
  • マクロ

Readlineの構造

  • ループで構成されている
  • 管理する文字バッファや文字列はCのcharだけで、コマンドでもマルチバイトは扱わない
  • キーシーケンスが最終的に編集コマンドに解決されたら、ターミナルの表示を更新して結果を反映させる
  • Readlineは編集バッファの中身を呼び出し元のアプリケーションに戻す

AOSA 3 The Bourne-Again Shell その3 #read_aosa

パース

  • パースエンジンが最初にする仕事は字句解析、つまり文字のストリームを単語に区切ってそれに意味を与えるということだ
  • エイリアスは完全な単語なので、エイリアスを使えば文法をも変えられる
  • エスケープを使えば特殊な意味をもたせることもできる
  • 字句解析器はクォートされた文字や文字列をパーサ側で予約語やメタ文字として扱わないようにする
  • パーサと字句解析器の残りのインターフェイスはそれほど難しいものではない
  • パーサはある程度の量の状態を符号化して解析器と共有し、文法上必要となるコンテキスト依存の解析を行う
  • 字句解析器はその筋では素養のない人が作っているので、オレオレパーサになっている
  • 予約語や代入分などを実現するために
  • コマンドのパースがどこまで進んだか
  • 複数行の文字列("ヒアドキュメント"と呼ばれることもある)を処理しているところかどうか
  • 条件分岐の中にいるかどうか
  • シェルパターンを展開したものを処理しているのか複合代入文を処理しているのか など

  • パース段階でのコマンドの置換が終わったことを判断する作業のほとんどは、ひとつの関数(parse_comsub)にまとめられている

  • 単語の展開の際にコマンド置換を展開するときには、bashはパーサを使って言語構造の終了位置を見つける
  • パーサはまた、yyparseを再帰的に起動する前にパーサの状態を保存しておかなければならない
  • パーサが返すのはコマンドを表すCの構造体(ループのような合成コマンドの場合は、その中にさらに別のコマンドが含まれる場合もある)で、それがシェル操作の次のステージ、つまり単語の展開処理に渡される

クォート処理

"" '' などの処理すべてをあらわす

m-takagi/aosa-ja · GitHub

AOSA 3 The Bourne-Again Shell その4 #read_aosa

  • パースが終わったら、実行の前に、パース段階で生成された単語の多くを展開することになる。つまり、(たとえば)$OSTYPEを文字列"linux-gnu"に置き換えたりするような処理だ
  • 変数は、わずかな例外を除いて文字列として扱われる(型付けが存在しない)
  • シンタックスシュガー

展開方法

(特殊なやつ) * チルダ * $(())

$ echo pre{one,two,three}post
preonepost pretwopost prethreepost
 diff <(ls -a README.md) <(ls -a README2.md)
1c1
< README.md
---
> README2.md

単語の分割→グロブ→実装

実装

  • シェルの基本構造がパイプラインにそったものなら、単語の展開は自分自身に向けた小さなパイプラインとなる(という基本の設計思想)
  • 単語の展開における各ステージは、単語を受け取って何らかの変換を施し、それを次の展開ステージに渡す。すべての単語展開が終わったら、コマンドを実行する
  • ユーザーの都合に合わせて機能を追加していくと設計がぐちゃぐちゃになる
  • パーサと同様、単語展開のコードもマルチバイト文字を正しく扱うことができる。たとえば、変数の長さの展開($#variable)は、バイト数ではなく文字数を数える。展開のコードは、展開の終わりやマルチバイト文字列で特別な意味を持つ文字を正しく識別する

m-takagi/aosa-ja · GitHub

AOSA 3 The Bourne-Again Shell その5 #read_aosa

コマンドの実行その1

  • bashの内部パイプラインにおけるコマンド実行ステージは、実際のアクションが発生する場所である
  • コマンド名の部分が読み込んで実行するファイルとなり、残りの単語はargvの残りの要素となる(ls -al hogeでいうと、lsがコマンドで、-al hogeがargvの部分)

リダイレクト

  • 最近追加されたリダイレクト構文によって、シェルに適切なファイルディスクリプタを選ばせてそれを指定した変数に代入できるようになり、ユーザーがファイルディスクリプタを指定する必要はなくなった
  • シェルは、ファイルシステムから実行して新しいプロセスを立ち上げるコマンドとシェ ル自身が実行する(組み込みの)コマンドの区別を意図的に曖昧にしている
ファイル記述子は、オープン中ファイルの詳細を記録しているカーネル内データ構造(配列)へのインデックスである

ファイル記述子 - Wikipedia

  • 複数のリダイレクトを単純にオブジェクトのリストで実装しているので、取り消しに使うリダイレクトは別のリストとして保持する
  • 過去のバージョンのBourneシェルでは、ユーザーが操作できるファイルディスクリプタが0から9までだけだった。10以上は、シェルが内部的に使うために予約されていたのだ。Bashではこの制限を緩め、プロセスのファイルオープンの上限に達するまで任意のディスクリプタを操作できるようにした

POSIX

各種UNIX OSを始めとする異なるOS実装に共通のAPIを定め、移植性の高いアプリケーションソフトウェアの開発を容易にすることを目的としてIEEEが策定したアプリケーションインタフェース規格

POSIX - Wikipedia

組み込みコマンド

  • Bashには、多数のコマンドがシェル自身の一部として組み込まれている
  • これらのコマンドはシェルから実行されるもので、新たなプロセスは立ち上げない
  • コマンドを組み込みで用意する主な理由は、シェルの内部状態を保ったり変更したりするためだ
  • Bashの組み込みコマンドは、シェルのその他の部分と同じ内部プリミティブを使う。組み込みコマンドはC言語の関数を使って実装されており、この関数は単語のリストを引数として受け取る

m-takagi/aosa-ja · GitHub

AOSA 3 The Bourne-Again Shell その6 #read_aosa

コマンドの実行その2

単純なコマンドの実行

  • シェルの変数への代入(つまり、var=value形式の単語)は、それ自身が単純なコマンドの一種
  • 指定したコマンド名がシェルの関数や組み込みコマンドにないものであった場合、bashはファイルシステムから、その名前の実行可能なファイルを探す。環境変数PATHの値は、このときの検索先を表すディレクトリをコロン区切りでつなげたリストである
  • PATHの検索でコマンドが見つかれば、bashはそのコマンド名とフルパス名をハッシュテーブルに保存する
  • 実行するファイルが見つかれば、bashはそれをフォークして新しい実行環境を作り、この新しい環境でプログラムを実行する

ジョブ制御

  • シェルによるコマンドの実行には二通りの方法
  • フォアグラウンド
  • バックグラウンド
  • ジョブ制御とは、プロセス(実行されたコマンド)をフォアグラウンドとバックグラウンドの間で移動したり実行の一時停止や再開をしたりする機能のこと
  • この機能を実現するためにbashはジョブという概念を導入した
  • シェルは、いくつかのシンプルなデータ構造を使ってジョブ制御を実装している
  • ジョブは、プロセスのリストとジョブの状態(実行中、停止中、終了など)、そしてジョブのプロセスグループIDで管理されている

複合コマンド

  • 複合コマンドは単純なコマンドのリストで構成
  • 実装方法は、パーサが複合コマンドに対応するオブジェクトを組み立て、コマンドを解釈するときにはそのオブジェクトを走査していくという方法
  • forの場合、予約語inに続く単語のリストを展開しなければならない。それから、展開された単語を順にとりあげて適切な変数に代入

m-takagi/aosa-ja · GitHub

AOSA 3 The Bourne-Again Shell その7 #read_aosa

bashの開発で学んだこと

  • ChangeLogを詳しく書く(そのとき変更したことを後から思い出せる)
  • 回帰テストをプロジェクトの立ち上げ時から組み込んでおくべき(非対話的な処理はすべてカバーしている)
  • 標準規格は重要。ソフトウェアの標準化作業も重要(さまざまな機能やその振る舞いについて議論できるだけでなく、規格化するときの参考にする標準を持っていればうまくいくから)
  • 外部の標準も重要だが、内部的な標準を持つこともまた大切
  • よくできたドキュメントは不可欠(成功したソフトウェアはドキュメントがいっぱい作られる。そのとき開発者自身のドキュメントは非常に重要。故に成功する前に予め書いておく)
  • よいソフトウェアはどんどん使うべき
  • ユーザーコミュニティは必要。批判的な声を責められていると思わないこと

  • 後方互換性の重要性(新たに作られる製品の機能が、旧世代での古い機能を満たすように考慮されること。でも具体的になんの話をしているのかは知らない)

互換性 - Wikipedia

バッカス・ナウア記法 - Wikipedia

結論

Bashは、大規模で複雑なフリーソフトウェアのよい例(おそらくbashを使っていることを意識していない人もいるだろう)

bash←GNU+コミュニティ+フィードバック

m-takagi/aosa-ja · GitHub

AOSA 4 Berkeley DB その1 #read_aosa

Oracle資料

http://www.oracle.com/technetwork/jp/ondemand/db-new/ord-seminar-bdb-v1-20101027-251331-ja.pdf

Berkeley DBの5つの特徴

• 組み込みに適した小型データベース • KVS型のAPIとSQLの2種類のAPIをサポート • オープンソース • 各種プラットフォーム等の対応 • Oracle DatabaseとのSync機能

NoSQLの走り

セカンダリインデックス
  • セカンダリインデックスの内容は、自動的に維持管理される
  • セカンダリインデックスへ直接更新したり挿入したりはしない
  • プライマリへの挿入はセカンダリへの追加になる
  • プライマリデータを削除するとセカンダリも削除される
DBハンドル

DBハンドルを使い、データベースアクセスが可能なので、コードベースでDBを操作できる

概要

  • コンウェイの法則によると、ソフトウェアの設計はそれを作った組織の構造を反映したものとなるらしい

Margoさん ファイルシステムとデータベース管理システムは本質的には一緒で、実装が少し違うだけ Bosticさん ツールベースのアプローチによるソフトウェア開発

  • Berkeley DB
  • さまざまなモジュールの集まりで作られている
  • それぞれがUnixの思想である"ひとつのことをうまくやらせる(do one thing well)"を体現していることもわかる

アーキテクチャへの注目

  • 最初はどんな設計だったのか
  • 最終的にどうなったのか
  • 状況に合わせて変化する設計

はじまり

UnixオペレーティングシステムがAT&Tに独占されていたころに、AT&TのプロプライエタリなソフトウェアをBerkeley SoftwareDistributionから取り除く作業

不自由なソフトウェアは邪悪である

ささやかな目標

  • インメモリのハッシュパッケージであるhsearchやディスク上でのハッシュパッケージであるdbm/ndbmを置き換えること
  • 大きなアイテムを扱えるようにする
  • 他の人のハッシュテーブルに関する研究の流用
  • アプリケーション側からはデータベースハンドルを通じてハッシュテーブルあるいはBtreeを参照でき、データベースハンドルがデータの読み込みや変更をするメソッドを保持

  • B+link→Btree

  • 1.85 Btree

  • 2.0 トランザクション
  • 3.0 作り直し
  • 4.0 レプリケーション
  • 5.0 SQL

設計講座1

複雑なソフトウェアパッケージをテストしたり保守したりしていく上で必須なのが、ソフトウェアをうまくモジュール分割した設計にしておいてモジュール間を
よくできたAPIで連携させることだ。

モジュールを細かくしないといずれ開発が破綻してしまう

複数の実装を共通のインターフェイスで扱えるようになっている。オブジェクト指向の見た目を持っているが、実際のところこのライブラリはCで書かれているのだ

継承じゃなくて、メッセージのほう。オブジェクト指向でやるとコーディング量が減るよってはなし

アーキテクチャの概要

  • 初期の構想と2.0の時点でプロセスマネージャーなるものが削除されている
  • バージョンアップ後でアーキテクチャが複雑化している
  • レプリケーション機能がまったく新しいレイヤーとしてシステムに追加された
  • logモジュールをlogとdbreg(database registration)に分割
  • すべてのモジュール間呼び出しを先頭にアンダースコアをつけた名前空間にまとめた
  • ログ出力サブシステムのAPIがカーソルベースとなった
  • アクセスメソッドの中のfileopモジュールがデータベースの作成・削除・リネームをトランザクション内でできるようにした(その必要はあるのか)

ずいぶんと高機能なデータベース

設計講座2

ソフトウェアの設計というものは、単に問題の全体像を把握してから解決を試みようという流れを強制するための手段のひとつにすぎない
熟練したプログラマーは、その目的を達成するためにさまざまなテクニックを使う
Berkeley DBの場合は、まず最初に完全なUnixマニュアルページを作るところから始めた
  • コードを全く書かずにマニュアルだけを揃えた
  • コードを書いてデバッグが始まってからプログラムのアーキテクチャについて考えるのは難しく、デバッグを始めた時点でのアーキテクチャがリリース時まで引き継がれる
  • アーキテクチャをきちんとしないとコードを書くのは難しい
  • 大規模なアーキテクチャの変更をしようとすると、それまでのデバッグの労力が無駄になってしまうこともよくある だろう

m-takagi/aosa-ja · GitHub

AOSA 4 その2 アクセスメソッド #read_aosa

アプリケーション組み込み型のデータベースライブラリ Berkeley DB - Wikipedia

Berkeley DBには4つのアクセスメソッドがある

名前 内容
Btree 可変長のキー/バリューペアに対応 キーの参照の局所性を提供する
Hash 可変長のキー/バリューペアに対応 キーの参照の局所性を提供しない
Recno レコード番号/バリューペアに対応 レコードレベルのロックに対応していない
Queue レコード番号/バリューペアに対応 レコードレベルのロックに対応している

Berkley DBの設計はCRUD機能をキーベースで行うのがもともとだったけども、カーソル機能を追加したらコードがぐちゃぐちゃになった

一連のデータに順にアクセスする際の検索条件および「現在位置」を保持するデータ要素

カーソル (データベース) - Wikipedia

ルール

  • コードの可読性を落としたり複雑化させたりしてしまうような最適化は、本当にそれが必要となるまで決してしてはいけない
  • ソフトウェアのアーキテクチャはソフトウェアに加えた変更の数に正比例して退化する

AOSA 4 その3 ライブラリインターフェイス層 #read_aosa

Berkeley DB - Wikipedia

設計講座

  • 変数やメソッドや関数の名前の付け方、コメントの書き方、そしてコードの書き方については「良い書き方」が存在するが、それより大切なのは命名規約やコーディングスタイルの一貫性を保つことである

  • ソフトウェアアーキテクトはアップグレードに伴ういざこざへの対応に気をつける必要がある

Berkeley DB

分割することで大掛かりな操作があったときにレプリケーション環境で実際にどんなアクションが必要なのかを判断しやすくなった。コードベースに手を加える作業を数えきれないほど繰り返した結果、すべての事前チェックを分離することが出来た。