bashシェルに新機能を追加するBashDiff(パート2)

 前回の記事では、bashシェルの機能を拡張するBashDiffというパッチについて紹介した。BashDiffの適用によりbashで利用可能となるコマンドや文字列の構文解析については、先に解説したとおりである。今回はその続きとして、位置パラメータの操作法、XMLの構文解析、ISAMやリレーショナルデータベースとの通信、GTK+2 GUIの作成、その他若干のテクニックや注意事項を紹介することにする。

位置パラメータの操作

 BashDiffから提供される位置パラメータの操作用コマンドは、多くの場合、ノーマル状態のbashよりもかなり効率的な処理を可能にしてくれる。例えばその1つであるpcursorは、複数の位置パラメータにおける現在値を1つのグループにまとめて取り扱えるようにし、これらのクリアおよび、スタックへの待避や取り出しを一括して扱うためのコマンドだ。位置パラメータそのものを操作するその他の追加コマンド群には、pp_というプレフィックスが付けられている。またオプション扱いではあるが、これらのコマンドでは-aパラメータを指定することで、明示的な名前を付けた配列にて位置パラメータを格納させることも可能だ。例えば下記のサンプルは、ノーマル状態およびBashDiff適用後のbashにおける位置パラメータの追加コマンドを比較したものである(後者が下側)。

$ set -- $@ Z
$ pp_append Z

 実際、pp_系コマンドの多くは、ノーマル版bashシェルでの操作よりも処理速度が大幅に向上している。もっとも、pp_プレフィックスの付いたコマンド群がどの程度有用かは、個々のユーザにおける位置パラメータの使用頻度で決まるものであるが、その効果は特にループ処理にて顕著に現れることになるだろう。例えば下記のサンプルコードはその極端な実装例で、負荷の大きいプリペンド操作を1つのループ中で1,000回実行させている。この実行結果を見ると、pp_バージョンのコードの方が非常に効率的な処理となっていることが分かるだろう。ただしループ回数を100に下げた場合、ノーマル版bashコマンドでも0.15秒で終了している。確かにこの場合においてもBashDiff版の方が所要時間0.006秒という点で高速ではあるが、こうした位置パラメータ操作の100回ループが他のスクリプトから何度も呼び出されるのでなければ、ノーマル版bashの処理であっても許容範囲内と見なせるかもしれない。

$ pp_trim 10000
$ time for i in `seq 1 1000`; do set -- Zoldpre $@; done

real	0m10.157s
user	0m9.880s
sys	0m0.004s

$ pp_trim 10000
$ time for i in `seq 1 1000`; do pp_push Zoldpre; done

real	0m0.088s
user	0m0.036s
sys	0m0.002s

XMLファイルのパース

 次にBashDiffでサポートされる、ExpatというXMLパーサについて見てみよう。このexpatビルトインの呼び出しで処理できるのは、1度に1つのXMLファイルだけだが、XMLの処理中はその中に含まれる、エレメント、コメント、名前空間などが検出されるごとに、指定した1群のコールバックが呼び出されていく。そしてBashDiffは、いくつかのハウスキーピング作業をユーザの代わりに自動で処理してくれる。例えば、XML_TAG_STACK配列にはXMLの全エレメントの名前が格納されるので、カレントのエレメントを特定することに利用できるし(コールバック時)、XML_ELEMENT_DEPTHを調べればカレントXMLエレメントの階層を確認できるのだ。なおXML属性は、各属性別の明示的なコールバックで個別処理をするのではなく、すべての属性がパラメータ群に渡された上で、XMLエレメントのコールバックが開始されるようになっている。

 Expatコールバックのエントリポイントとしては、bashの関数群を利用するのが便利だろう。下記のサンプルコードは、ugu.xmlという簡単な構成のファイルを、BashDiffのexpatビルトインを用いて構文解析させるというものだ。ここでのstartelem関数の役割は、新規XMLエレメントの処理開始を行うことで、ノーマル版bashのdeclare関数を用いて、呼び出し時におけるXML_TAG_STACK配列の内容を表示させている。そしてこのサンプルコードでは、XML属性がどのように処理されているかを示すため、BashDiffによりstartelemに渡される2つ目のパラメータを出力させるようにしている。

$ cat ugu.xml
<ugu foo="bar" linux="fun">
 <nested one="two" three="four" />
</ugu>
$ startelem() {
  declare -p  XML_TAG_STACK;
  echo $2;
}
$ expat -s startelem ugu.xml
declare -a XML_TAG_STACK='([0]="ugu")'
foo=bar
declare -a XML_TAG_STACK='([0]="nested" [1]="ugu")'
one=two

データベースの操作

 BashDiffパッチの適用は、gdbmISAMファイルのサポートも可能にしてくれる。データベースの構築と値のセットはgdbmビルトインにて行うが、その際にはファイル名およびキーと値のペアを指定しなくてはならない。また-eオプションを付けてgdbmを実行すると、特定のキーがセットされているかの確認ができ、セットされていた場合は、どのような値と対応付けられているかも検証できる。特に後者の機能については、例えば、何らかのオプションとして追加の処理を行わせるタイプのシェルスクリプトを記述する際に役立つはずだ。その他のオプション機能としては、登録されたすべてのキーや値を取り込ませたり、gdbmデータベース全体を1つのシェル配列にインポートさせるという操作も可能となる。

 下記のサンプルコードでは、最初にtest.gdbというファイルに対して1組のキーと値のペアをセットしているが、操作対象のファイルが存在しない場合は自動で作成されるようになっている。次に行っているのは、キーと値の特定ペアの存在に関する検査をいくつか実行し、個々の結果を表示させるという作業だ。その後、2組のキーと値のペアを1度にセットしてから、-Wオプションを用いて、gdbmファイル全体を1つのbash配列に読み込むという作業を行っている。ここで最後に行っているのは、ノーマル版bashのdeclareコマンドを用いた、bash配列にインポートされたデータの内容表示である。なお、キーか値の一方だけを配列にセットしたければ、それぞれ-Kおよび-Vというオプションを使用すればいい。

$ gdbm test.gdb key value1
$ gdbm -e test.gdb key
$ echo $?
0
$ gdbm -e test.gdb key2
$ echo $?
1
$ gdbm -e test.gdb key value1
$ echo $?
0
$ gdbm -e test.gdb key value2
$ echo $?
1
$ gdbm test.gdb key2 value2 key3 value3
$ gdbm -W thedata test.gdb
$ declare -p thedata
declare -a thedata='([0]="key2" [1]="value2" [2]="key" [3]="value1" [4]="key3" [5]="value3")'

 SQLiteデータベースについても、BashDiff版シェルのLsqlコマンドにてデータ操作を行うことができる。このコマンドはLsql -d file.sqlite SQLquery という基本構文にて使用するようになっている。ただし、この形式によるLsqlの実行結果はすべて標準出力に渡されるが、オプション扱いの-aを用いてbash配列の名前を指定しておくと、実行結果を単純に出力させるのではなくデータとして取得することができる。

 下記のサンプルコードはSQLiteデータベースのデータを本格的に操作するものではないが、このコードにおけるrealdata.sqliteにデータベースを格納させておけば、テーブルjoinなどの具体的な作業をSQLコマンドに行わせた上で、その結果をサンプル中のtable配列のような形式で取得するといった拡張ができるはずだ。

$ Lsql -a table -d realdata.sqlite "select 'foo','bar','bill','ted';"
$ declare -p table
declare -a table='([0]="foo" [1]="bar" [2]="bill" [3]="ted")'

 PostgreSQLおよびMySQLデータベースについても、PsqlおよびMsqlというコマンドを用いることで、Lsqlと同様の手順にて接続することができる。ただしPsqlおよびMsqlには、Lsqlの-dに相当するオプションは用意されておらず、データベースの位置や接続用のログイン認証に関する情報は、他のパラメータ群を用いてBashDiffに渡さなくてはならない。

GUIプログラミング

 操作性に優れた簡易的なGUIをbashにて使用したいというユーザの場合、BashDiffにより追加されるgtkコマンドを試してみるべきだろう。その起動構文はgtk gtk.xmlという非常にシンプルなもので、ここで使うXMLはリダイレクトさせることもできる。この機能については、bashでのXMLの使用によるGUIの構築という点に難色を示すユーザがいるかもしれないが、ここで用いるXMLスキーマは極めて単純なものだ。GUIを構成するオプションやボタンの類は、bashのシェルスクリプトにて作成でき、これらを実行すれば必要なXMLエレメントが出力されるようになっている。そして、ユーザによるボタンクリックで実行させるbashコマンドの設定といった、GUIとの情報交換は、commandなどの特殊なXML属性を介して行えばいい。同様に、コンボボックスやテキストフィールドにてユーザが行った設定内容については、id属性にて指定したシェル変数にGUI終了時点で格納するという方式になっている。

 なお今回のテスト中、gtkコマンドによるsegfaultが何度か発生したが、後半部のテストでは、この問題はそれほど再発しなかった。

 下記のサンプルXMLは、簡単なテキスト入力フィールドを備えたGUIを作成するためのもので、ここではOKボタンのクリックで、ユーザの入力値を$entryというシェル変数に格納させるようにしている。

$ cat gtk2.xml
<dialog border="10" buttons="gtk-ok,gtk-cancel" id="dialog">
    <entry id="entry" initial="initial text"/>
</dialog>
$ gtk gtk2.xml
$ echo $entry
this is the text

その他の機能

 BashDiffの適用によるめぼしい機能拡張はこうしたものだが、その他にもこのパッチは、雑多な構文および機能的な追加をbashに施すことになる。例えば、インプット時のインデントを保持させたい場合に役立つのが<<+によるヒアドキュメント機能だ。

 同じくvplotコマンドは、1つまたは2つのbash配列からデータを取得して、ターミナル上でX-Yプロットを行うという機能を有している。この入力データについては配列名を指定する他に、コマンドライン上でx1 y1 x2 y2といった形式での座標データを直接入力することもできる。

 BashDiffパッチの適用ではRPN式の計算機も追加され、PHPと同様の方式にて、1つのファイル上でテキストとbashコマンドを混在させることも可能となる。

 その他には、標準入力経由で受け取るカード読取装置の生データからの情報抽出および、クレジットカード番号用の操作を行うcardswipecreditcardのように、実際の用途はかなり限られているであろうコマンドも追加される。仮にこれらのコマンドを実用に供する場合は、特定メーカ製のPOS用ハードウェアに対応したprotobaseコマンドも併用することになるだろう。

 BashDiffでは、文字の種類などを検証するctype.hの機能も多数取り込まれるようになっている。ただし残念ながら今回の試験では、これらの機能を正常に処理させることはできなかった。つまり下記のサンプルのようにisnumberをそのまま実行すると、判定可能な文字種の指定パターンが一覧されるのだが、その1つを用いて、大文字のGに対するisnumber upper Gという判定を行わせると、本来は正しいと評価されるべきなのに“invalid number”とされてしまうのだ。

$ isnumber
isnumber: usage: isnumber { alnum | alpha | ascii | blank ...
  | cntrl | digit | graph | lower | print | punct | space ...
  | upper | xdigit | letter | word } number...

$ isnumber  upper G
bash+william: isnumber: G: invalid number

 以上、BashDiffというパッチについて長々と紹介したが、実際にこれらの機能がノーマル状態のbashシェルを置き換えるだけの価値を有すかは、かなり微妙な判断となるだろう。確かにBashDiffを適用すれば、caseコマンドなどでの不一致処理といった、スクリプティング作業を簡単化してくれる多数の機能が利用可能となるのは間違いないのだが、仮にこうしたオプションが使えないとしても、ノーマル状態のbash固有の機能だけで同等のスクリプトを組むのは、多少手間はかかるものの決して不可能ではないのだ。とは言うものの、ログインシェルとしてのbashに適用するかは別問題として、リレーショナルデータベースや大規模なルックアップテーブル(gdbm)を扱うユーザであれば、これらの操作機能をBashDiffを介して導入しておいても損はないのではなかろうか。

Ben Martinは10年以上にわたってファイルシステムに取り組んでおり、博士課程の修了後、現在はlibferris、ファイルシステム、検索ソリューションを中心としたコンサルティング業に従事している。

Linux.com 原文(2008年10月29日)