bashの内蔵コマンドを自作してみた
bashでは、内蔵コマンドを個別に有効にしたり無効にしたりできます。さらに、共有ライブラリで内蔵コマンドを追加できる機能もあります。この機能には、あまり日本語のドキュメントがありませんが、ごく簡単な例で試してみました。以下にまとめてみますので、間違いや、よりよい方法などがありましたら、ご指摘ください。
内蔵コマンドにすると何がうれしい?
普通の外部コマンドやシェル関数ではなく内蔵コマンドとして実装するメリットとしては、bashの内部の状態を操作できることがあります。そこで、ここではシェル変数に値を代入するコマンド「mysetvar」を追加してみます。「my」と付けたのは、「オレオレsetvar」とでもいうような意味です。
mysetvarは、以下の形式で呼び出すことにします。
mysetvar 変数名 値
「変数名=値」に比べると、変数名に変数展開などが適用されるという違いがあります。Lispでいえば、setqに対するsetのような。そこまで必要なシェルスクリプトは、あまり書かないとは思いますが。
前準備をする
ここでは、Ubuntu Linux 8.10の環境で試してみます。UbuntuやDebianでは、bash-builtinsパッケージをインストールすると、必要なヘッダやサンプルをインストールしてくれます。
$ sudo apt-get install bash-builtins
/usr/share/doc/bash/examples/loadables/の下にサンプルのソースが置かれます。ほぼこれがドキュメントがわりです。
もちろん、Cコンパイラなどの開発環境も忘れずに用意しておきます。
Cのソースはこんな構造で書く
さっそくコードを書きましょう。ここでは、mysetvar.cというファイル名で新規作成します。
まず先頭で、bashの内蔵コマンドに必須のヘッダファイルを指定します。
#include <builtins.h> #include <shell.h>
ほかに、シェル変数を操作するためのヘッダファイルも指定します。
#include <variables.h>
コマンドの本体は、以下のような関数になります。関数名は、「コマンド名_builtin」とつけるのが定番っぽいです。中身については後述します。
int mysetvar_builtin(WORD_LIST *list)
{
/* 処理 */
return EXECUTION_SUCCESS;
}
続いて、ドキュメント文字列を定義します。bashで「help コマンド名」を実行すると、この次で出てくるusage文字列に続いて、ドキュメント文字列が表示されます。
char *mysetvar_doc[] = {
"set VALUE to VARIABLE",
(char *)NULL
};
ドキュメント文字列は、1行ずつの文字列へのポインタの配列となっていて、最後はNULLポインタで終えます。ドキュメント文字列の変数名は、「コマンド名_doc」とつけるのが定番っぽいです。
最後に、コマンドの情報をまとめた構造体を定義します。
struct builtin mysetvar_struct = {
"mysetvar", /* コマンド名 */
mysetvar_builtin, /* 本体の関数へのポインタ */
BUILTIN_ENABLED, /* 初期状態 */
mysetvar_doc, /* ドキュメント文字列へのポインタ */
"mysetvar VARIABLE VALUE", /* usage文字列 */
0 /* 予約領域 */
};
引数と戻り値を見てみる
では、改めて関数本体を見てみます。
int mysetvar_builtin(WORD_LIST *list)
{
/* 処理 */
return EXECUTION_SUCCESS;
}
関数には、コマンドライン引数が展開されたデータがリスト構造で渡されます。そして、成功したらint型の値EXECUTION_SUCCESSを返します。
ここで作るmysetvarは、2つの引数をとります。引数がない場合はusageを表示してエラー終了することにします。引数が0個の場合は、リスト構造が最初からNULLポインタになっているので、チェックします。
if (list == NULL) {
builtin_usage();
return EX_USAGE;
}
そうでなければ、list->word->wordが1つ目の引数(の文字列へのポインタ)になります。さっそくCの変数varに入れてみます。
char *var = list->word->word;
list->nextをたどると、2つ目の引数以降のリストになります。
list = list->next;
1つ目の引数と同じように、NULLポインタだったら2つ目の引数が指定されていないので、usageを表示してエラーにします。
if (list == NULL) {
builtin_usage();
return EX_USAGE;
}
list->word->wordが2つ目の引数なので、Cの変数valに入れます。
char *val = list->word->word;
シェル変数をいじる
ようやくシェル変数を操作するコードを書きます。まず、1つ目の引数が変数名として問題ないかチェックします。
if (! legal_identifier(var)) {
sh_invalidid(var);
return EXECUTION_FAILURE;
}
ではシェル変数に値をセットします。
bind_variable(var, val, 0);
最後に、シェルが特別扱いする変数であれば処理を実行するようにします。
stupidly_hack_special_variables(var);
これで完成です。
ちなみに、メモリの確保や開放がこれでいいのかどうかは、ちょっと自身がありません。
改めてコード全体を見る
以上で書いたmysetvar.cの内容を、改めて見てみましょう。
#include <builtins.h>
#include <shell.h>
#include <variables.h>
int mysetvar_builtin(WORD_LIST *list)
{
if (list == NULL) {
builtin_usage();
return EX_USAGE;
}
char *var = list->word->word;
list = list->next;
if (list == NULL) {
builtin_usage();
return EX_USAGE;
}
char *val = list->word->word;
if (! legal_identifier(var)) {
sh_invalidid(var);
return EXECUTION_FAILURE;
}
bind_variable(var, val, 0);
stupidly_hack_special_variables(var);
return EXECUTION_SUCCESS;
}
char *mysetvar_doc[] = {
"set VALUE to VARIABLE",
(char *)NULL
};
struct builtin mysetvar_struct = {
"mysetvar",
mysetvar_builtin,
BUILTIN_ENABLED,
mysetvar_doc,
"mysetvar VARIABLE VALUE",
0
};
コンパイルして共有ライブラリを作る
できたmysetvar.cをコンパイルして、共有ライブラリmysetvar.soを作ります。
$ cc -I/usr/include/bash -fpic -shared -o mysetvar.so mysetvar.c
ここで、/usr/include/bashディレクトリを指定しているのは、UbuntuやDebianでの指定だと思います。
実行してみる
実行してみましょう。現在のbashで試すのは怖いので、サブシェルを起動します。
$ bash
起動した新しいbashで、さきほどのmysetvar.soを読み込みます。それには、「enable -f ファイル名 コマンド名」を実行します。
$ enable -f ./mysetvar.so mysetvar
ではmysetvarを実行します。
$ mysetvar a abc
変数の値を表示してみましょう。
$ echo $a abc
うまくいったようです。
念のため、readonlyした変数にmysetvarで値を設定してみます。
$ readonly b def $ mysetvar b ghi bash: b: readonly variable
ちゃんとエラーになりました。
最後にサブシェルを終了します。
$ exit
まとめ
- bashの内蔵コマンドは、共有ライブラリから追加できる
- 関数、ドキュメント文字列、struct builtinを定義する
- enable -fで読み込む
コメント
はじめまして
コメントの投稿
トラックバック
http://emasaka.blog65.fc2.com/tb.php/499-07209948
