termcapとterminfo

termcapとterminfo
端末エミュレータとと切っても切れない関係にあるのが、termcap&terminfoデータベースです。このtermcap&terminfoデータベースですが、書かれている内容が文字や記号の羅列で意味がとりづらく、「よくわからないけれどこう書けば動く」というような黒魔術的な扱いがされる事も多いようです。

今回はこのtermcap&terminfoデータベースについて書いてみたいと思います。

どんな物か

termcapやterminfoは、端末の動作の違いを吸収するためにあります。

dateやcatなどの大多数の一般的なコマンドでは文字列を順番に出力するだけなので、端末の違いが影響する事はほとんどありません。これに対しviなどの画面全体を使うようなプログラムでは、カーソルを指定した位置に移動したり、指定した位置に新しい行を挿入するといった操作が必要になります。こういった操作は端末に対して命令として解釈される文字列(制御シーケンス)を出力して行うのですが、この命令用の文字列は端末の種類によって違ったりします。そこでプログラムは、使用している端末(環境変数TERMで指定します)で使いたい機能がどのような文字列になるかをtermcapやterminfoで調べて出力します。

termcap/terminfoデータベースの内容

termcap/terminfoデータベースには端末間の動作の違いを吸収する為の情報が書かれています。
どのような情報があるかというと、

  • 端末の特徴
  • 端末に動作を指示する為の文字列
  • 特殊キーが押された時の内容

などがあります。

まず“端末の特徴”ですが、端末の横幅や高さとか、使える色数、オートラップ(右端まで文字を出力した時に自動で改行する)機能が有るかなどの情報の事です。
“端末に動作を指示する為の文字列”は“どんな物か”で挙げたような、カーソルを指定位置に移動したり、画面をクリアしたりする時にどのような文字列を送ればいいかという情報です。
“特殊キーが押された時の内容”は、カーソルキーやファンクションキーが押された時にどのような文字列が送られて来るかという情報です。

それでは、端末の情報がtermcap/terminfoデータベースの中でどのような形で書かれているかを見ましょう。

termcapエントリの例

まずはtermcapです。termcapデータベースは、通常 /etc/termcap というファイルに一つにまとまって書かれています。その中で xterm 用のエントリは以下のようになっています。

xterm|xterm terminal emulator (X Window System):\
	:am:bs:km:mi:ms:xn:\
	:co#80:it#8:li#24:\
	:AL=\E[%dL:DC=\E[%dP:DL=\E[%dM:DO=\E[%dB:IC=\E[%d@:\
	:K2=\EOE:LE=\E[%dD:RI=\E[%dC:SF=\E[%dS:SR=\E[%dT:\
	:UP=\E[%dA:ae=\E(B:al=\E[L:as=\E(0:bl=^G:bt=\E[Z:cd=\E[J:\
	:ce=\E[K:cl=\E[H\E[2J:cm=\E[%i%d;%dH:cr=^M:\
	:cs=\E[%i%d;%dr:ct=\E[3g:dc=\E[P:dl=\E[M:do=^J:ec=\E[%dX:\
	:ei=\E[4l:ho=\E[H:im=\E[4h:is=\E[!p\E[?3;4l\E[4l\E>:\
	:k1=\EOP:k2=\EOQ:k3=\EOR:k4=\EOS:k5=\E[15~:k6=\E[17~:\
	:k7=\E[18~:k8=\E[19~:k9=\E[20~:kD=\E[3~:kI=\E[2~:kN=\E[6~:\
	:kP=\E[5~:kb=\177:kd=\EOB:ke=\E[?1l\E>:kh=\EOH:kl=\EOD:\
	:kr=\EOC:ks=\E[?1h\E=:ku=\EOA:le=^H:mb=\E[5m:md=\E[1m:\
	:me=\E[0m:mm=\E[?1034h:mo=\E[?1034l:mr=\E[7m:nd=\E[C:\
	:rc=\E8:sc=\E7:se=\E[27m:sf=^J:so=\E[7m:sr=\EM:st=\EH:ta=^I:\
	:te=\E[?1049l:ti=\E[?1049h:ue=\E[24m:up=\E[A:us=\E[4m:\
	:vb=\E[?5h\E[?5l:ve=\E[?12l\E[?25h:vi=\E[?25l:\
	:vs=\E[?12;25h:

terminfoエントリの例

terminfoデータは通常、/usr/share/terminfo や /usr/share/lib/terminfo (OSによって違う)の下に、端末名の一文字目のディレクトリがあり、その中に1エントリにつき1ファイルの形で置かれています。
例えばxtermの場合 /usr/share/terminfo/x/xterm になります。
このファイルはコンパイル済みのデータの為、そのままでは人間が読めません。
このデータは infocmp コマンドを使う事により、人間が読める形にして出力できます。
以下は、infocmp xterm の出力内容です。

#       Reconstructed via infocmp from file: /usr/share/terminfo/78/xterm
xterm|xterm terminal emulator (X Window System),
	am, bce, km, mc5i, mir, msgr, npc, xenl,
	colors#8, cols#80, it#8, lines#24, pairs#64,
	acsc=``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,
	bel=^G, blink=\E[5m, bold=\E[1m, cbt=\E[Z, civis=\E[?25l,
	clear=\E[H\E[2J, cnorm=\E[?12l\E[?25h, cr=^M,
	csr=\E[%i%p1%d;%p2%dr, cub=\E[%p1%dD, cub1=^H,
	cud=\E[%p1%dB, cud1=^J, cuf=\E[%p1%dC, cuf1=\E[C,
	cup=\E[%i%p1%d;%p2%dH, cuu=\E[%p1%dA, cuu1=\E[A,
	cvvis=\E[?12;25h, dch=\E[%p1%dP, dch1=\E[P, dl=\E[%p1%dM,
	dl1=\E[M, ech=\E[%p1%dX, ed=\E[J, el=\E[K, el1=\E[1K,
	flash=\E[?5h$\E[?5l, home=\E[H, hpa=\E[%i%p1%dG,
	ht=^I, hts=\EH, ich=\E[%p1%d@, il=\E[%p1%dL, il1=\E[L,
	ind=^J, indn=\E[%p1%dS, invis=\E[8m,
	is2=\E[!p\E[?3;4l\E[4l\E>, kDC=\E[3;2~, kEND=\E[1;2F,
	kHOM=\E[1;2H, kIC=\E[2;2~, kLFT=\E[1;2D, kNXT=\E[6;2~,
	kPRV=\E[5;2~, kRIT=\E[1;2C, kb2=\EOE, kbs=\177, kcbt=\E[Z,
	kcub1=\EOD, kcud1=\EOB, kcuf1=\EOC, kcuu1=\EOA,
	kdch1=\E[3~, kend=\EOF, kent=\EOM, kf1=\EOP, kf10=\E[21~,
	kf11=\E[23~, kf12=\E[24~, kf13=\E[1;2P, kf14=\E[1;2Q,
	kf15=\E[1;2R, kf16=\E[1;2S, kf17=\E[15;2~, kf18=\E[17;2~,
	kf19=\E[18;2~, kf2=\EOQ, kf20=\E[19;2~, kf21=\E[20;2~,
	kf22=\E[21;2~, kf23=\E[23;2~, kf24=\E[24;2~,
	kf25=\E[1;5P, kf26=\E[1;5Q, kf27=\E[1;5R, kf28=\E[1;5S,
	kf29=\E[15;5~, kf3=\EOR, kf30=\E[17;5~, kf31=\E[18;5~,
	kf32=\E[19;5~, kf33=\E[20;5~, kf34=\E[21;5~,
	kf35=\E[23;5~, kf36=\E[24;5~, kf37=\E[1;6P, kf38=\E[1;6Q,
	kf39=\E[1;6R, kf4=\EOS, kf40=\E[1;6S, kf41=\E[15;6~,
	kf42=\E[17;6~, kf43=\E[18;6~, kf44=\E[19;6~,
	kf45=\E[20;6~, kf46=\E[21;6~, kf47=\E[23;6~,
	kf48=\E[24;6~, kf49=\E[1;3P, kf5=\E[15~, kf50=\E[1;3Q,
	kf51=\E[1;3R, kf52=\E[1;3S, kf53=\E[15;3~, kf54=\E[17;3~,
	kf55=\E[18;3~, kf56=\E[19;3~, kf57=\E[20;3~,
	kf58=\E[21;3~, kf59=\E[23;3~, kf6=\E[17~, kf60=\E[24;3~,
	kf61=\E[1;4P, kf62=\E[1;4Q, kf63=\E[1;4R, kf7=\E[18~,
	kf8=\E[19~, kf9=\E[20~, khome=\EOH, kich1=\E[2~,
	kind=\E[1;2B, kmous=\E[M, knp=\E[6~, kpp=\E[5~,
	kri=\E[1;2A, mc0=\E[i, mc4=\E[4i, mc5=\E[5i, meml=\El,
	memu=\Em, op=\E[39;49m, rc=\E8, rev=\E[7m, ri=\EM,
	rin=\E[%p1%dT, rmacs=\E(B, rmam=\E[?7l, rmcup=\E[?1049l,
	rmir=\E[4l, rmkx=\E[?1l\E>, rmm=\E[?1034l, rmso=\E[27m,
	rmul=\E[24m, rs1=\Ec, rs2=\E[!p\E[?3;4l\E[4l\E>, sc=\E7,
	setab=\E[4%p1%dm, setaf=\E[3%p1%dm,
	setb=\E[4%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m,
	setf=\E[3%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m,
	sgr=%?%p9%t\E(0%e\E(B%;\E[0%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m,
	sgr0=\E(B\E[m, smacs=\E(0, smam=\E[?7h, smcup=\E[?1049h,
	smir=\E[4h, smkx=\E[?1h\E=, smm=\E[?1034h, smso=\E[7m,
	smul=\E[4m, tbc=\E[3g, u6=\E[%i%d;%dR, u7=\E[6n,
	u8=\E[?1;2c, u9=\E[c, vpa=\E[%i%p1%dd,

データ形式

termcap/terminfo 共に # で始まる行はコメントであり、実際の動作には影響を与えません。
まずコメントを除いた後の1行目ですが、これは端末名を表しています。端末名は | で区切られて複数記述でき、その内最後の物は端末の詳細な名前となっています。環境変数TERMに設定する値は、この最後の詳細名以外のどの名前でも使用できます。

2行目以降は端末の機能(capability)が、termcapでは : で、terminfoでは , で区切られて 列挙されています。
capabilityには、”論理型”, “数値型”, “文字列型”の三種類の型があります。

論理型

このタイプは機能の有無を表す物で、例えばオートラップ機能を表すam (termcap/terminfo共通)が有ります。
オートラップ機能が有る端末エントリには am を記述し、無い端末では記述しないというように、capability の有無で表します。

数値型

このタイプは端末の横幅や色数などの数値を表す物です。記述の仕方は capability名#数値 という形で、例えば端末の横幅が80桁で有るのを表すのは co#80 (termcap) や cols#80 (terminfo) となります。

文字列型

このタイプはその名の通り文字列を表す物で、端末に動作を指示する為の文字列を記述したり、端末で特殊キーが押された時に送られてくる文字列を表したりするのに使われます。記述の形式は、capability名=文字列 という形になります。
例えば、画面消去(termcap:cl, terminfo:clear) は \E[H\E[2J となっていますが、これは ESC [H ESC [2J を端末に送ると画面が消去されカーソル位置がホームポジション(左上隅)に移動するという事を表しています。(\E は ESC文字(0x1b) を表します)
また、上矢印キー(termcap:ku, terminfo:kcuu1) は \EOA となっているので、ESC OA という文字列が端末から送られてきたら、上矢印キーが押されたとして処理を行うようにします。

文字列型のcapabilityの中には、パラメータを受ける物があります。例えばカーソル位置移動は、行位置および桁位置をパラメータとして使用します。この場合、termcapとterminfoでは少し記述方法が変わります。

まずtermcapですが、カーソル位置移動である cm は \E[%i%d;%dH と記述されています。この内 %i と %d がパラメータの動作に影響します。
まず %i ですが、これはすべてのパラメータに 1 を足すという意味です。これはtermcapでは左上の行/桁位置を 0, 0 として扱いますが、vt100やxtermでは左上は 1, 1 から始まる為、vt100の座標に合わせる為に 1 を足します。
次に %d ですが、これはパラメータを10進数の数値として出力します。
この結果、例えばカーソル位置を5行目の10桁目(termcapの座標位置で4,9)に移動する為には、ESC [5;10H という文字列を出力すればよい事になります。

terminfoはスタックベースの言語となっており、termcapに比べて記述が少し複雑になりますが、条件分岐といった複雑な動作を行う事が出来ます。
terminfo でカーソル移動である cup は \E[%i%p1%d;%p2%dH となっています。この内、%i はtermcapと同じで、パラメータの数値に1を足します。
次の %p1 ですが、最初のパラメータをスタックに積みます。そして次の %d でスタックの先頭の数値を10進数として出力します。
同様に %p2 で2番目のパラメータをスタックに積み %d で出力します。
結果としてtermcapと同じように ESC [5;10H という文字列が出力されます。

端末エントリの書き換え

通常はシステムが提供しているデータベースをそのまま使いますが、端末の動作を変える為にエントリを書き換えたくなる事があるかもしれません。
例えば代替画面バッファを無効にしたい場合などです。
termcap/terminfoでは、カーソル移動機能の使用開始/終了を指示するためのcapabilityが有ります。ですが、このcapabilityは実際には全画面を使うプログラムの初期化/終了処理として使われれる事が多いです。
この初期化/終了処理に代替画面バッファへの切り替え命令を含める事によって、viやless等で代替画面バッファを使うようになっています。
ここでは代替画面バッファを使用しないようにtermcap/terminfoを変更してみます。

termcapの場合

termcapでは全画面用の初期化処理(カーソル移動使用開始)の為のcapabilityは ti で、終了のcapabilityは te となっています。
この ti と te の内容を無効化すれば、代替画面バッファが使われないようになります。
capabilityを無効にするには対象の端末エントリに含まれるcapabilityを削除すればいいのですが、capabilityの後ろに@を付ける事によって無効にしたと明示する事も出来ます。例えば ti,te と無効にする場合、 :ti@:te@: というように記述します。

termcapのエントリを変更する時、システム全体での動作を変更する方法と、特定のユーザでのみ変更する方法の二種類があります。

termcap:システム全体で変更する場合

システム全体で変更する場合は /etc/termcap ファイルを編集して、目的の端末エントリ(この場合はxterm)の ti および te を :ti@:te@: というように書き換えます。

システムによっては、目的の端末エントリが以下のようになっているかもしれません。

xterm|X11 terminal emulator:\
        :tc=xterm-new:

tc というcapabilityはベースとなるエントリを示す物で、定義されていない設定はこのtcで指定されたエントリの物が使われます。
この場合、参照先のxterm-newを変更する方法も有るのですが、以下のように ti および te を明示的に無効にし、残りのcapabilityはxterm-newを参照するようにする事が出来ます。

xterm|X11 terminal emulator:\
        :ti@:te@:tc=xterm-new:

termcapではすべての端末エントリが一つのファイルにテキスト形式で入っています。そのため登録されている端末データ多くなると読み込みに時間がかかるようになります。
この読み込みに時間がかかる事への対処として、FreeBSD等の一部のOSではtermcapファイルをデータベース形式に変換して使用しています。
この場合、termcapファイルを書き換えただけでは反映されないので、以下のように cap_mkdb コマンドを使ってデータベースを更新する必要があります。

# cap_mkdb /usr/share/misc/termcap
termcap:ユーザ毎の設定

管理者権限が無くてシステム全体の設定が変更出来ない時や、他のユーザへ影響を出したくない場合は、ユーザ毎にtermcapエントリを上書きするように設定します。
ただ、このユーザ毎でのtermcapエントリの設定方法は、termcapの実装によって違いがあり、設定が非常に面倒な場合がありますので注意して下さい。

まず確実に使える方法ですが、環境変数 TERMCAP に該当の端末エントリでtiおよびteを無効にした内容を設定します。この時、tcによる他のエントリの参照は使わないで下さい。

export TERMCAP=xterm:am:~略~:ti@:te@:~略~:vs=\E[?12;25h:
export TERM=xterm

実装によっては tc による他のエントリの参照が使える場合が有ります。この場合は、

export TERMCAP=xterm:ti@:te@:tc=xterm-new:
export TERM=xterm

というような設定にします。
また、実装によっては $HOME/.termcap から端末エントリを読み込む物もあります。この場合、$HOME/.termcap に以下のような記述をします。

xterm:ti@:te@:tc=xterm-new:

terminfoの場合

terminfoでの該当の capabilityは、smcup と rmcupなので、この二つを無効に設定します。
既に書きましたが、terminfoのデータは人間が読める形式では保存されていません。
そこで、infocmp で可読形式で出力 → 編集 → tic でコンパイル という手順をとります。
まず、下記のように infocmp コマンドで可読形式の端末情報を出力します。

# infocmp xterm > xterm.ti

出力されたファイルをエディタで編集し、smcup および rmcup を、smcup@, rmcup@ のように書き換えます。
最後に編集した端末情報を再度コンパイルします。システム全体に反映する場合は、管理者権限を持つアカウントで

# tic xterm.ti

を実行します。

個別のユーザの環境のみに反映する場合は、

% tic -o ~/.terminfo xterm.ti

を実行します。
実装によってはデフォルトで ~/.terminfo のデータも読み込むため上記の手順だけでも反映されますが、念のため環境変数TERMINFOを設定しましょう。

export TERMINFO=~/.terminfo
You can leave a response, or trackback from your own site.

Leave a Reply

Subscribe to RSS Feed Follow me on Twitter!