## Linux Backspace/Delete mini-HOWTO

Sebastiano
Vigna

    vigna@acm.org

anonymous
日本語訳
v1.4
7 December 2000
Updated for Red Hat 7.0 and Helix Gnome conflicts.
v1.3
18 October 2000
Name change.
v1.2
15 October 2000
Updated. Added "What If Nothing Works" section.
v1.1
13 September 2000
Added tcsh fixes
v1.0
5 September 2000
First release

## はじめに

早い遅いの違いはあったろうが、 Linux ユーザなら誰でも次のような状況にハマったことがあるだろう。 コンソール上や X 上で、Backspace キーと Delete キーが効かなくなってしまうという現象だ。 この文書は、こういったことがなぜ起こるのかを説明し、その対処法を紹介する。 ここに示されている考えは、基本的にはどのディストリビューションにも 適用できる。ただしシステムの設定ファイルは各ディストリビューションによって かなり違うので、ここでは読者が自分自身でどのファイルを 修正すればよいのかがわかるだけの情報を、必要に応じて提供しようと思う。

ここで言う Backspace キーとは、1 文字分もどって そのカーソル上にある文字を消去するものとする。一方 Delete キーは 現在のカーソル上にある文字をカーソルを動かさずに消去するものとする。もしこの 2 つの機能が 入れ換わっていたほうがいいと思うなら - ほとんどのキーボードの Backspace キーの表面には左向きの矢印 (←) が書かれているのだが - この文書は直接の 解は与えない。が、確実に役に立つ情報が得られるだろう。

もうひとつの仮定は、変更する部分はローカルな (そのユーザだけの) ファイルだけにしたい、ということだ。ディストリビューション標準の設定は どこも変更すべきではない。最後にもうひとつ。この文書では アプリケーションが正しいイベントを受け取れるようにシステムを設定する方法を 説明する。もしお使いのアプリケーションが風変りなやり方でそのイベントを 解釈するようならば、とりうる唯一の修正方法はアプリケーションの設定を 変更することだけだ。

この Mini-HOWTO の最初のリリース以降、事情は更に複雑になってきた。 同一の端末エミュレータ (Red Hat 7.0 や 7.0 より前の Helix/Red Hat における gnome-terminal など) でも、ディストリビューションが 違うと別の ASCII 文字列を生成することがある。 この不一致のため、今や端末データベースはそれらが記述するとされている 端末エミュレータにさえ合致していない。以下の議論の基盤をしっかりさせるため、 我々は基本的に Debian キーボードガイドラインで示されている設定を正しい設定として扱う。

## どのようにしてキー入力が動作 (Action) になるのか

キーボード上のあるキーが押されると、いくつものハードやソフトの部品が 協力して、その実際のキーで意図された意味 (文字を表示するとか) を実行する。 とりあえずここでは (ハードウェアの部分はいじれないので) ソフトの側、 それもコンソール出力に関連したイベントに注目してみよう。

1. キーを叩くと生のキーボードスキャンコードが生成され、 これがキー番号に変換される。i386 上のシステムでは、 普通 BackSpace キーのキー番号は 14 番 になり、Delete キーは 111 番になる。

2. キー番号は、キーボードライブラリによってキーシンボル (keysym) に変換される。これにはユーザがロードした キー定義ファイルが使われる。キーボードデータベース (例えば RedHat なら `/usr/lib/kbd/` 以下) を覗くと、たぶん何種類もの 違ったコンピュータやレイアウト、そして違った解釈 (2 つの Alt キーに別々の働きをさせたい人もいる) のキー定義ファイルがあるのが わかるだろう。Linux コンソールのレイアウトでは、keysym の Delete はキー番号の 14 番に割り当てられており、 Remove が 111 番になっている。ヘンだと思うかもしれないが、 Linux コンソールは VT100 端末をエミュレートするためこうなっているのだ。

3. まだ先がある。コンソール上のアプリケーションは ASCII 文字列を読む。keysym ではない。だからコンソールは keysym を読んで、 その内容をきちんとエンコードした ASCII 文字列に 変換してやらなくてはならない。たとえば Linux コンソールでは Delete の keysym が ASCII コードの 127 番 (DEL) にマップされている。一方 Remove keysym はエスケープシーケンスになる。BackSpace keysym は ASCII コードの 8 番 (BS) になる。

4. 最終段階は、ある意味ちょっと遡ることになる - それぞれのキーによって 生成された ASCII 文字列をキー特性 (key capability) に戻すのだ。ここでは端末データベース が用いられる。このデータベースには、それぞれの端末に応じた、文字列からキー特性 (要するに keysym の一部) への対応表が入っている。

   まずいのは、標準 とされている端末データベースには 2 つの種類があるということだ - termcap と terminfo だ。 ディストリビューションにもよるが、どちらか片方だけを使っている かもしれないし、アプリケーションにもよるかもしれない。解説は主に新しい terminfo 向けだが、文中の対応策では 両方を考慮している。

   たとえば Linux コンソールでは、F1 キーは エスケープ文字 + `[[A` という文字列を生成する。 端末データベースにあるコンソールの項目を参照すると、 これは `key_f1` というキー特性に変換されることがわかる (この項目を実際に見てみたければ、`infocmp linux` と打ってみるといい)。GNU の termcap マニュアルには、端末データベースに関する 非常に徹底した優れた説明が載っている。普通、Linux のアプリケーションは、 より新しい形式である terminfo データベースを使う。 これは ncurses パッケージに 含まれているものだ。

   たぶんここまで来ればもうそんなに驚かないだろうが、terminfo データベース中の Linux コンソールのエントリでは、 DEL を `kbs` (バックスペースキー) 特性に対応させている。そしてエスケープに続く `[3~` という文字列を `kdch1` (delete-one-char キー) 特性に対応させている。 これまでは Backspace キーが DEL を出力するのを見て 奇妙に思っていたかもしれないが、端末データベースはすべてを正しく元に 戻しているのであって、正しい振る舞いをしているアプリケーションなら DEL を `kbs` キー特性として解釈するのだ。 だからカーソルの左にあるキャラクタを消すというわけだ。

## なぜ (ときどき) 動かないのか

これで基本的なことは分かってもらえただろうか。キーボードとコンソール アプリケーションの間にはボトルネックがある。つまり、 ASCII 文字列でしかやり取りできない部分があるのだ。 だから、特殊キーをやり取りするには、まず keysym から ASCII 文字列に変換し、しかるのちにキー特性へ変換するという作業が必要になる。 コンソールが異なれば、キーの変換方式も変わってくる。というわけで、 端末データベースの出番となるわけだ。端末データベースを作成すれば システムは問題なく動作するだろうが、小さな問題が 1 つ残る。 つまり、端末データベースがいつも正しく設定されているとは限らないし、 全員が同じデータベースを使うわけでもないということだ。

アプリケーションは、データベースに登録されたエントリのうちの どれを使うかを、何らかの方法で知る必要がある - これは `TERM` 環境変数を適切に設定することで実現する。だが時に、端末エミュレータと `TERM` によって示されたデータベースエントリの内容との間に 不一致があることがある。

もっと言えば、多くのアプリケーションは端末データベースを 使わない (か、少なくとも全部は使わない)。そして、 BS や DEL の ASCII コードを 勝手に解釈する - つまりデータベースを見ずに、それらが本来持つべき意味を 割り当てるのだ (通常ならば当然このセマンティクスの意味は、カーソルの前 あるいは下で文字を削除することだ)。その結果我々の美しいシステムは完全に 壊れてしまうわけだ (全ての Linux ユーザーが苦い経験をしたことがあるように)。 例えば bash は DEL を backward-delete-char すなわちバックスペースすべきと想定する。 だからインストールしたばかりのコンソールの上では Backspace キーは予想通りの動作をする。2 回のあべこべが重なりあったからだ! 当然、Delete キーは動かない。これは bash が 端末データベースを見ていないので、 `kdch1` 特性であることがわからないからだ。

どれだけ物事がこんがらがってしまっているかを表す一例として、 Red Hat ディストリビューション (多分他のにも) に含まれている `fix_bs_and_del` スクリプトを考えてみよう。 それはその場しのぎに BackSpace keysym を Backspace キーに割り当て、Delete keysym を Delete キーに割り当てる。これでシェルが動くようになった! でも今度は不幸なことに keysym 生成と端末データベースの正しい 組み合わせに頼っているプログラムが全てまったく動かなくなってしまう。 Delete keysym が DEL にマップされ、 それが今度は terminfo データベースの `kbs` キー特性に マップされてしまうために、両方のキーがバックスペースを発生させてしまうからだ。

## X

X 環境下においても大きな違いはない。X Window System という 新しいレイヤーは、スキャンコードをコンソール上より多種かつ正確な keysym に翻訳して アプリケーションに渡してくれるのだ (これが XEmacs では問題が発生しない理由だ - X はキーコード 14 を keysym BackSpace に割り当て、キーコード 111 を keysym Delete に割り当てる。 こうすることで目的の動作を関連付けることが容易になる)。当然だが 端末エミュレータプログラム (X の世界では通常 VT100 エミュレータを指す) は X Keysym を ASCII 文字列に割り振らねばならない。結果としてまた例の問題と 再会することになる。

もっと詳しく言うと、普通 xterm はコンソールと まったく同じように振る舞う (つまりまったく同じ ASCII 文字列を出力する)。しかし例えば、 7.0 より前の Red Hat についてくる gnome-terminal は、Backspace で BS を、Delete で DEL を 出力する。これを知ったらほんとに笑えるだろうが、 xterm と gnome-terminal はデフォルトで端末データベースの同じエントリを使うのだ。 ここでは `kbs` 特性は ASCII DEL に割り当てられている。というわけで、本来まともな振る舞いをするアプリケーションのはずが、 gnome-terminal 上では Backspace と Delete で同じ動作をしてしまうことになる。 こういうときは次の一行

    bash$ export TERM=gnome

でまともなアプリケーションなら問題を解決できる。ええと、 いつもこうってわけではないのだが。システムに `gnome` という名前の端末データベースエントリがない場合も あるかもしれないから。とくに最新のものじゃないデータベースの場合は。

いずれにせよこれが常に解決策であるとは限らない - 例えばもし 7.0 以降の Red Hat ディストリビューションを使っているなら、 gnome-terminal はコンソールのような動作をするだろう。 しかし気を付けよう - もし Helix ディストリビューションを使うように デスクトップをアップグレードすると、gnome-terminal が 7.0 より前の Red Hat のような振る舞いをするようになる。

以下の議論を簡単にするために、標準を コンソールのように振る舞う VT100 エミュレータ、アレゲを Backspace に対して BS を、Delete に対して DEL を出力する VT100 エミュレータ、と定義する。 従って、例えば Debian ディストリビューションの xterm は 常に標準であり続けたが、 Red Hat では標準からアレゲに、そしてアレゲから標準にと 何度か切り替わった。gnome-terminal の履歴は よりいっそう異常になっている。

## アプリケーションを書く時に何をすべきか

コンソールアプリケーションを書くときには、ユーザーに親切に。 そして標準入力から何が来るのかを、以下の処理フローを使って 理解するように努めること。

1. 正しい terminfo エントリを開き、 その文字シーケンスが現在の端末上で特殊な意味を持つのかどうかを探そう。 もしそうなら、その terminfo セマンティクスを使う。

2. 行送り、改行、タブ、そしてもちろん BS と DEL は、ASCII のもともとの 意味で使う。お祈りも役に立つかもしれない。

## あなたのシステムですべきこと

繰り返すが、システムを直そうとする人々が混乱する主な原因は、 たいてい間違った箇所を直そうとしているからだ。正常に作動しているように 見える部分は偶然そう見えるだけなことも多いので、ある箇所が壊れていると 思って修理をすすめると、正しい設定を間違った設定に変更してしまう ことがよく起こりがちだ。

### やらねばならないこと

#### 微妙にずれてるアレの検出

綺麗な解決法への最初のステップは、まずどの端末が異常で どれが正常かをはっきりと知ることだ。普通、端末は全てコンソールのように 振る舞うので、その場合は全体を正常にするための変更は最小で済む。 だがもしアレゲ端末 (例えばアレゲバージョンの gnome-terminal) を使っている場合は、 特殊な扱いが必要となる。

次の C の一行プログラム

    void main(void) {int c; while(c = getchar()) printf("%d 0x%02X\n", c, c);}

が役に立つかもしれない。この 1 行を `ascii.c` という名のファイルに記録し、`gcc ascii.c -o ascii` でコンパイルした後、`./ascii` とタイプして実行し、 何かキーを押してから RETURN を押してみよ。 このプログラムは 10 進法と 16 進法で、発生した ASCII 文字列を表示する (`stty erase ^-` を最初に行っておけば、実際のコード全てが得られる)。これで Backspace が何をするか簡単に見られるようになった。もしそれが DEL (127) を出力するならそれは標準的な端末、 もし BS (8) ならばアレゲ端末、ということになる。

#### 各エミュレータの分別

アレゲ端末エミュレータを使っている場合は、それらを 標準的なものと区別せねばならない。理論上はこれは可能なはずだ。 なぜなら端末データベースには異なったシーケンスを持つ端末のためのエントリが 数種類あるからだ。

ここで我々が取るアプローチは、`gnome` エントリを すべてのアレゲ端末に、`xterm` エントリをすべての 標準端末に用いる、というものである。このやり方は大抵のディストリビューションで うまくいく (5.0 以前の Red Hat のような `xterm` エントリがアレゲな一部例外を除く)。

だが、gnome-terminal はデフォルトで xterm と同じエントリを使用する。 よってもし一方がアレゲで、もう一方がそうでない場合、 それらを分離するための方法を探す必要がある。理論的には gnome-terminal の `termname` オプションでユーザは `TERM` 変数をより相応しい名前に 変更することが可能なはずだが、現在 gnome-terminal 1.2.1 においてこのオプションは動作しない。

ここで一つよいアイデアを。gnome-terminal は `COLORTERM` 変数を `gnome-terminal` に設定することを利用するのだ。 だから、簡単なテストをシェルの設定ファイルに追加すれば、 `TERM` 変数を修正することが可能だ。

#### 端末データベースの修正

次の問題は端末データベースにアレゲ端末向けの `gnome` エントリが存在しない可能性があることだ (これは termcap と terminfo のいくつものバージョンで起こりうる)。 最近の terminfo データベースには `gnome` エントリがあるはずだが、いずれにせよ gnome-terminal は基本的には xterm で、例のキー二つが違うだけだ。 だから、ちょっとした仕掛けで正しいエントリを新たに生成することは 可能なはずだ。

#### シェル動作の修正

bash など多くのプログラムで入力行を 読むのに使用されている readline ライブラリは、 指定したシーケンスを認識するようにカスタマイズすることが可能だ。 この変更は `TERM` 変数によって制御することも可能なので、 一度端末の分別が済めば、より細かいチューニングをキーボードに 施すことができる。

さらに、もし less などの raw 行入力を 行うアプリケーションを正常に動作させたければ、 アレゲ端末エミュレータでは削除文字は BS であり、 DELではないのだとシェルに理解させなければならない (標準端末なら Backspace キーは既に DEL を発信しているから、何もする必要はない)。 これは `stty` コマンドを用いることで実現可能だ。

### 実践方法

これらの修正方法には幾つか欠点がある。まず、これらは特定の 端末でしか機能しない。また、理論上 (実際に起こることは稀なのだが) これらは他の端末の readline ライブラリを 混乱させる可能性がある。 もっとも、これらの制限はほとんどの場合は問題にならない。

まず `infocmp gnome` として、既に `gnome` エントリが terminfo データベースに 存在しているかどうかを確認する (termcap は後で修正する)。エントリが存在しなければ、以下のコマンド

    bash$ tic <(infocmp xterm |\
            sed 's/xterm|/gnome|/' |\
            sed 's/kbs=\\177,/kbs=^H,/' |\
            sed 's/kdch1=\\E\[3~,/kdch1=\\177,/')

を用いれば `~/.terminfo` に正しいエントリが作成される。 もし同じコマンドが root により実行されると、グローバルデータベースに エントリが作成される (この動作は `TERMINFO` を `~/.terminfo` に設定すれば変更できる)。 もし `xterm` エントリが既にアレゲである場合は (例えば 5.0 以前の Red Hat の場合)、このスクリプトはそのエントリを変更せずに そのままコピーしてくれる。これは望みどおりの動作だろう。

次に以下の一行を `~/.inputrc`

古いバージョンの bash では、 `INPUTRC` を正しく設定する必要があることを覚えておこう。 例えば

    export INPUTRC=~/.inputrc

という行を `~/.profile` (またはそれ以外のログインシェルのみが 読み込むファイル) に追加する。 に追加する。

    "\e[3~": delete-char

この一行は readline ライブラリに、標準 Delete キーを標準的なエミュレータでどのように扱うかを指示する。 運がよければ他の端末とは干渉しないだろう。だが、次にライブラリに対し DEL 文字がアレゲ端末上で何を意味するか説明せねばならない。 例えば以下の 3 行を `~/.inputrc` に追加する。

    $if term=gnome
    DEL: delete-char
    $endif

もし xterm がアレゲである場合も、 それに対応する 3 行を追加せねばならない。一方もしアレゲな 端末エミュレータがなければ、この作業は必要ない。 これらの変更は `/etc/inputrc` ファイルを変更すれば グローバルに有効になる。

`TERM` 変数が正しく設定されていれば、 条件付き割り当てを使うことでアレゲ端末を正しく動作させることは可能だ。 これを確かめるにはいくつもの方法がある。まず gnome-terminal での `TERM` 変数の デフォルト値は `xterm` だから、もしアレゲ端末が 存在しないければ、我々は何もしないでよい。だが、もし `xterm` をデフォルトにしているアレゲ端末が存在する場合、`TERM` 変数を 正しく設定する方法を見つけなければならない - gnome-terminal がこれに当てはまるとしよう。

もっとも簡単に `TERM` 変数の設定を行うには、 gnome-terminal を `--termname=gnome` 引数付きで起動すればよい。これは GNOME パネル上のラウンチャーに正しいコマンドラインを 設定することなどで可能だ。もし古いバージョンを使っている場合、 この方法は動作しないかもしれない。その場合以下の数行を

    if [ "$COLORTERM" = "gnome-terminal" ]
    then
        export TERM=gnome
    fi

`~/.bashrc` 設定ファイル

より正確には、ログインシェルだけでなく全てのシェルに読み込まれる シェル設定ファイルに、だ。どれが正しいファイルかは、 bash のスタートアップシーケンスに依存する。 に追加すればよい。

この割り当ては gnome-terminal 上でのみ実行され、 `TERM` 変数を正しく設定する。

端末を `gnome` に設定すると、 `ls` は色表示を行わなくなる。 多くのバージョンの `ls` は gnome-terminal の色特性を知らないためだ。 これを避けるには、 `~/.dircolors` 設定ファイルを `dircolors --print-database >~/.dircolors` として作成し、 設定ファイルに `TERM=gnome` という一行を追加すればよい。

アレゲな端末エミュレータ向けの termcap エントリを動的に作成させることもできる。以下に手順を説明する。 いつものように、 `~/.bashrc` に追加する:

    if [ "$TERM" = "gnome" ]
    then
        export TERMCAP=$(infocmp -C gnome | grep -v '^#' | \
                        tr '\n\t' '  ' | sed 's/\\  //g' | sed s/::/:/g)
    fi

最後に、削除キーにより生成される文字を端末デバイスに 教えてやらねばならない。削除キーは通常バックスペースであると期待されているために、 標準的な端末エミュレータではこれは DEL 文字になる。 だからまずそのようにセットしてから、各アレゲ端末用の条件文を追加していく。 いつも通り `~/.bashrc` に追加する:

    stty erase ^?

    if [ "$TERM" = "gnome" ]
    then
        stty erase ^H
    fi

もし全ての端末エミュレータがアレゲならば、 削除文字を条件なしで設定してももちろん構わない。

一部のディストリビューションは、既にこの修正をシステムワイドな `/etc/inputrc` 設定ファイルなどに施していることがある。 この場合 `~/.inputrc` から不要になった行を 削除することもできる。

### tcsh向けの修正

tcsh の場合、修正は全部 `~/.tcshrc` に施す必要がある。 以下のように bash の場合と似ている -

    bindkey "^[[3~" delete-char

    if ($?COLORTERM) then
       if ($COLORTERM == "gnome-terminal") then
          setenv TERM gnome
       endif
    endif

    stty erase ^?

    if ($?TERM) then
       if ($TERM == "gnome") then
          setenv TERMCAP \
           "`infocmp -C gnome | grep -v '^#' | tr '\n\t' '  ' | sed 's/\\  //g' | sed s/::/:/g`"
          bindkey "\177" delete-char
          stty erase ^H
       endif
    endif

二つ目の部分は全てのアレゲ端末用に複製せねばならない。 当然、termcap が既に存在するなら作成する 必要はない。

## 何をやってもダメな場合

最初にすることは、どの ASCII コードが発生しているかを C の 1 行プログラム を使って確認することだ。

どのシーケンスが発生しているかわかったら、現在の terminfo エントリを `infocmp` を使って確認する必要がある (大量の情報が表示されるが恐れないように!)。 そして `kbs` と `kdch1` 特性が 正しいシーケンス (対応するキーから発生するもののこと) と一致することを確認する。 さらに、`stty -a` を用いて、削除文字は Backspace キーによって生じるものであることを確認せねばならない (`^H` は BS を意味し、`^?` は DEL を意味することに注意)。

もし不一致があった場合、いくつかの理由が考えられる - `TERM` 変数の値が違う、端末データベースのエントリが違う、 X の端末エミュレーションが違う、などだ。 ここまでで、あなたが自分で調査を進めるに十分な情報を得てくれたことを期待する。

もし各アプリケーションが異なる動作をする場合、それらのいくつかは 端末データベースを正しく活用していて、残りはそれができていない可能性が高い。 一部のアプリケーションで各キーが正しい動作をしたとしても、 それはそのアプリケーションが端末データベースを正しく使用していることを 保証するわけではない - 覚えているだろうか？それは偶然動いているだけかもしれない。 もしそれらを個別にチェックしたければ `ne` エディタを 試してみるとよい。`ne` は `kbs` と `kdch1` を含む端末特性の全てを利用し、予想される意味は それ以外が全て適用外だった場合にしか利用されない。

## 結論

ここまでで推奨してきた修正で、書いた文章を消すにあたっての 問題の多くの部分が解決されるだろう (とはいえ、新しい文章を書くための 助けにはならない`:)`)。

全体の設定の中で小さいバグがある。gnome-terminal から xterm を開始した場合、`TERM` に `gnome` が設定されてしまう。この不便さは、もちろん無害だし、 gnome-terminal を `TERM` の適切な 設定とともに実行することが可能になれば解消されるだろう。

もう一つ、効果的な解決法のない、ちょっといらただしい問題は、 リモートからの接続に関係するものだ。端末データベースとつじつまの 合わないホストに接続した場合には、手動で設定をおこなう必要がある。

最後に一言言っておくべきこととしては、これらの修正は壊れたアプリケーション (たとえば、`kbs` キー特性を無視するアプリケーション) には通用しない。こうした場合にはどうしようもない。なぜなら一つの壊れた アプリケーションに合わせた修正をすると、他の行儀の良いアプリケーションを 壊すことになるからだ。

## 日本語訳について

この日本語訳は、匿名によるバザール的翻訳が可能かどうかという実験の一環として、 2 ちゃんねる掲示板 Linux 板の 連作で訳そう! mini-HOWTOs スレッドに書き込んだ匿名の有志が行いました。 複数の寄稿者により翻訳され、原著者の承諾も得ていますが、 匿名であるため訳者名は掲載しません。

この文書は、商用・非商用を問わず、自由に複製・頒布することができます。 その際は、LDP ライセンス に従ってください。

ご質問、および修正・提案等は、調整役の二之宮 祐樹 <gm@debian.or.jp> または JF ML <JF@linux.or.jp> にお願いします。