補完,ヘルプ,ヒストリ機能つきコマンドラインインタフェースライブラリ
説明
本ライブラリは,C言語で書かれたコマンドラインインタフェース(CLI)をもつアプリケーション向けの,フロントエンドライブラリです.LinuxやMacOSなどのPOSIX上で利用できます. ユーザの入力するコマンドラインは,fgetsやfscanfなどの標準Cライブラリを使って標準入力から取得できますが,これらのライブラリ関数を本ライブラリのGetString() APIで置き換えることで,補完や候補,およびヒストリ機能を利用できるようになります. またログイン時にはユーザの入力した文字はアスタリスクでマスクすることもできます.
ソースコードは以下のGithubから入手できます.
技術要素説明
本ライブラリは2つの技術要素から構成されます.ひとつめは,標準入出力のrawモード化です.コンソールやシリアルインタフェースでは,cookedモードとrawモードの2種類が設定できて,通常はcookedモードで利用されています.cookedモードは,ユーザが入力したキー操作をいったんカーネルモジュールが何らかの前処理を施してからユーザアプリケーションに渡します.一方rawモードでは処理をすることなく,そのままユーザアプリケーションに引き渡されます.例えば大抵のアプリケーションは^Cで終了され,アプリケーションは^Cを押されたことを直接検知するのではなく,アプリケーションの終了時の例外ハンドラで間接的に検知します. 本ライブラリを使うと,押されたキーが直接得られますので,例えばログイン中に押された^Cでアプリを終了させるのではなく,ログアウトしてログインプロンプトを表示する状態に移行させるようなプログラミングが可能です.
2つめの技術要素は,コマンドラインの補完,候補表示機能です. ヘルプ機能として,これまでに入力された文字列のパターンによって,次に受付可能な候補語を表示させたり,コマンドの綴りの一部を入力するだけで,残りの部分を補完するには,ライブラリはシステムの受付可能なコマンド体系を理解しておく必要があります.本ライブラリは,この機能を簡単に実装するために,表形式でコマンド文法を記憶します. 表形式を使うと,一語の長さや一行に入力できる語数に制限ができます.より柔軟な仕様にするには表形式ではなくリスト形式で実装すべきでしょうが,たいてい上限パラーメータは見通せるので表形式でも十分なケースが多く実装も簡単であるため,今回は表形式を使っています.
サンプルアプリのビルド
このライブラリは3つのC言語のソースコードから構成されます. その中のshell.cにサンプルのmain関数が付加されています. このmain関数を有効にするには,-DMAINオプション付きでコンパイルしてください. 単に,オプションなしでmakeすると,shell.c はmain付きでコンパイルされサンプルアプリケーションshellができます. LinuxやMacOSでビルドしてください.
$ make
shell.c をライブラリとして使用する場合には,-DMAINオプションをつけないでコンパイルします.
サンプルアプリshellを起動すると,loginプロンプトが表示されます.
$ ./shell
login:
サンプルアプリではパスワードはadminで,ハードコードされています.
login:*****
=>
入力した文字はアスタリスクでマスクされています.ログインに成功すると,コマンドプロンプト=>が表示されます.
誤ったパスワードを入力すると2秒間コンソールがブロックされます.単にslepp関数などを使ってfgetsやfscanfを一定時間呼ばないようにしてブロックする方法もありますが,ブロック中にユーザが文字入力すると,その文字は画面上に表示され,入力した文字列は入力バッファに取り込まれてます.本ライブラリのブロック関数では,ブロック中にユーザが^Cを含む文字入力をしても画面にその文字は表示されず読み捨てられます.
コマンド文法
サンプルを使ってみる前に,すでに定義されているコマンド文法を見ておきます.コマンド文法は以下の表にまとめています.例えば,showコマンドはstatus,agentなどのサブコマンドをとることができます. 各コマンドラインにはIDが付与されいますが,このIDの使い方は後に説明されます.
cmd# | col1 | col2 | col3 | col4 |
---|---|---|---|---|
1001 | show | status | ||
1002 | show | agent | < id > | |
1003 | show | agent | traf | < id > |
1004 | show | agent | route | < id > |
1005 | show | terminal | < id > | |
1005 | show | traf | ||
2001 | connect | open | < id > | |
2002 | connect | open | < id > | |
3001 | add | agent | < id > | |
3002 | add | terminal | < id > | |
4001 | delete | agent | < id > | |
4002 | delete | terminal | < id > | |
8000 | log | |||
9000 | exit |
機能
候補表示機能
プロンプトが表示されたとことでtabキーをおすと,入力可能なコマンド候補が表示されます.
=>*tab*
show connect add delete log exit
showコマンドを入力して,その時点でtabキーを押すとshowコマンドの受付可能なサブコマンドの候補が表示されます.
=>show *tab*
status agent traf terminal
このように,前に何が入力されたのかによって(文脈によって),入力される候補となる語が変化します.
補完機能
プロンプトのあと,sキーに続きtabキーを押すと入力中文字列は自動的にshowに変化します. 単語の一部を入力するだけで,残りの部分を補完します.
=>s *tab*
=>show
例えば,'delete terminal'というコマンドを入力する場合,文字列を全て綴るのではなく, 'd tab t tab' のわずか4キーストロークで十分です.
ヒストリ機能
何回かコマンドを入力した後,上矢印キーを押すことでこれまでに入力したコマンドラインを呼び出すことができます.呼び出した履歴の文字列は編集可能で,左右矢印キーでカーソルが移動します. 状態変化などを監視するために同じコマンドを繰り返し入力する場合には便利です.
ライブラリのアレンジメント
キーワードの抽出
アプリケーションごとにコマンドラインは異なりますので,まずコマンド文法を設計して,上記の表を作成します.コマンドラインは予約語とパラメータ(非予約語)で構成され, 先頭から1語以上の予約語が並び,そのあとにパラメータが続くようにします.表は予約語の並び方を表現しています.予約語の並び方は同じで,パラメータの個数が異なる場合でも,表は一行で表現します.例えばshow status というコマンドラインはパラメータなしでも,show status 1234というパラメータを取ることも可能です.
表が完成したら,そこから予約語を抽出します. 各々の予約語はrule.cのenum W_WORDでIDを割り当て,WORD_LIST構造体配列で,IDと実際の文字列を関係付けます.
文法登録
上記の表で定義されたコマンド文法の各行にはcmd#列でコマンドIDが割り当てられています.このIDはrule.hで宣言されます. コマンドIDはとびとびの数値でも構いませんが,一意でなければなりません.
この文法表で,コマンドIDと予約語の並びが確定したら,rule.cのCOMMAND_TREE型の配列treeにこの表を記録します.treeの各エントリは,コマンドIDに続き予約語の並びを定義します.予約語の並びは,文字列を並べるのではなく,予約語に割り当てた予約語IDを並べてゆきます.各エントリの並びの終わりはW_E(=0)で終端します.またtree配列の最終行の各フィールドにはW_B(=-1)をセットして最終行であることを表示しています.
使い方
API
本ライブラリのAPIは3つです.
シグネチャ
int GetString(char *ptr,size_t plen,char *prompt,int pshadow);
説明
ユーザからコマンドラインの文字列を得ます.本APIを呼ぶことで画面にプロプトが表示され,ユーザがリターンキーを押した時点で本APIから制御が戻ります. 補完,候補,ヒストリ機能は本APIの中で実行されるため,アプリケーションから見た機能はfgetと同じく本APIで一行の文字列を標準入力から入力するものになります.
パラメータ
ptr: ユーザの入力した文字が格納されるエリアの先頭アドレス.このエリアはアプリケーション側で確保管理してください.
plen: ptrの指すエリアのサイズ.1KBを推奨します.
prompt: 画面に表示するコマンドプロンプ
pshadow: 0=エコーバックあり, 1=エコーバックなし(ユーザ入力文字を非表示)
戻り値
0=成功 入力文字列はptrのエリアに格納済み
1=Ctl-C が押された.
シグネチャ
void SetCloseConsole(unsigned int sec);
説明
標準入力を一定の時間ブロックします.ログイン失敗時などにこのAPIを使って再ログインまでの時間を稼ぎます.
パラメータ
sec: 就寝時間を秒で指定
シグネチャ
int AnalyzeCmdLine(char *ptr,char params[COL_MAX][PARAM_LEN]);
説明
ユーザが入力したコマンドライン文字列をコマンドIDに変換します. 例えば,ユーザが'connect open 124 567'と入力すると,この文字列を本APIに渡すとコマンドID2001が返されます. コマンドには2つのパラメータ'123'と'567'が付加されていますが,それらはparamsの指す2次元文字配列に格納されます. 本APIを使えばアプリケーション側では,独自のコマンド分析器を作る必要はなく,コマンドIDでサービス関数種別を選択して,paramsに格納されたデータをその関数に渡すような簡単なプログラミングが可能です.
パラメータ
ptr:GetString APIで得られたコマンド文字列
params: パラメータを格納する2次元配列..このエリアはアプリケーション側で確保管理してください.
COL_MAX (=6)は一行に許容される最大トークン数(空白文字で区切られた語数).
PARAM_LEN (=100)は各パラメータの最大文字長
returns
コマンドID
API利用例
APIの使い方はshell.cのmain関数を参考にしてください.ログインをさせる場合には,GetString APIの第4パラメータを1にして,ユーザの入力した文字を伏字にします. 認証が成功するとループから抜け,失敗したら2秒間ブロックして再度パスワード入力を促します.
for(;;){
if(GetString(stbuff,1000,"login:",1)==1){
exit(1);
}
if(strcmp(stbuff,"admin")==0){
break;
}
SetCloseConsole(2);
}
ログイン中は'=>'というプロンプトを表示してユーザからの文字列入力を待ちます. 文字列入力があると,AnalyzeCmdLineでコマンドIDに変換してパラメータを表示しています. 実際のアプリケーションでは,コメントされた箇所に,コマンドIDによる分岐を行い,それぞれの個別処理に移ることになるでしょう.
for(;;){
if(GetString(stbuff,1000,"=>",0)==1){
printf("enter \"exit\" to quit\n");
continue;
}
printf("str=%s|\n",stbuff);
for(int i=0;i<COL_MAX;i++) params[i][0]=0;
cmd=AnalyzeCmdLine(stbuff,params);
printf("cmd=%d\r\n",cmd);
for(int i=0;i<COL_MAX && params[i][0];i++){
printf("P%d:%s|\n",i+1,params[i]);
}
/*
Call your Service Functions at this place.
*/
if(cmd==CMD_EXIT)
break;
}
注意
-プロンプトを含む全ての文字列は標準出力に出力されています. アプリケーションにネットワークを介してリモートアクセスさせる場合には本ライブラリに何らかの改造が必要になります.それには大きく2つの方法があリます.ひとつは,クライアント側に本ライブラリを実装する方法です.ライブラリはほとんど変更なしで流用可能ですが,サーバとクライアントの関係が強すぎて,サーバのコマンドを追加するだけでクライアントの修正も必要担います. 一方サーバに本ライブラリを実装する方法では,標準入出力の代わりにソケットが使われるため,多少の改造が必要になりますが,アプリケーション本体がメッセージを出力する出力先の変更と同程度の改造量になるでしう.
-本ライブラリをマルチスレッドアプリケーションに組み込む場合,本ライブラリのAPIはメインスレッドから呼び出すことを推奨します.
ライセンス
MIT