こんにちは!今日はANSIエスケープコードについてお話します。

長い間、私はANSIエスケープコードについて漠然と知っていました(「ターミナルでテキストを赤くする方法だよね」みたいな)が、それらがどこで定義されるべきか、または標準が存在するかどうかを実際に理解していませんでした。ただ漠然と「危険地帯」みたいな感覚を抱いていました。今年、ターミナルについて学ぶ中で、以下のことを知りました:

  1. ANSIエスケープコードは、ターミナルでの使い勝手の大幅な向上に寄与しています(リモートマシンにSSH接続しているときに、システムのクリップボードにコピーする方法があるのをご存知ですか?それはOSC 52というエスケープコードです!)
  2. それらは完全に標準化されておらず、そのため必ずしも確実に動作するとは限りません。また、不可視であるため、エスケープコードの問題のトラブルシューティングは非常に苛立たしいものです。

そこで、エスケープコードに関するいくつかの既存の標準を自分用にまとめてみました。なぜなら、それらが必ず信頼できず苛立たしいものでなければならないのか、それともより自信を持って頼れる未来があるのかを知りたかったからです。

エスケープコードとは?

ターミナルで左矢印キーを押したときに^[[Dが表示されたことはありますか? それはエスケープコードです!「エスケープコード」と呼ばれるのは、最初の文字が「エスケープ」文字で、通常ESC\x1b\E\033、または^[と表記されるためです。

エスケープコードは、ターミナルエミュレータがターミナルで実行中のプログラムとさまざまな種類の情報(色、マウスの動きなど)をやり取りするための手段です。エスケープコードには2種類あります:

  1. 入力コード:Unicodeに収まらないキー入力やマウス操作のために、ターミナルエミュレータが送信するものです。例えば「左矢印キー」はESC[D、「Ctrl+左矢印」はESC[1;5D、マウスクリックはESC[M :3のようになります。
  2. 出力コード:プログラムが出力してテキストに色を付けたり、カーソルを移動させたり、画面をクリアしたり、カーソルを非表示にしたり、テキストをクリップボードにコピーしたり、マウスレポートを有効にしたり、ウィンドウタイトルを設定したりするためのものです。

それでは、標準についてお話ししましょう!

ECMA-48

エスケープコードに関連する最初の標準として見つけたのは、1976年に最初に公開されたECMA-48です。

ECMA-48は以下の2つのことを行っています:

  1. エスケープコードの一般的な形式を定義する(「CSI」コード(ESC[ + 何か)や「OSC」コード(ESC] + 何か)など)
  2. 具体的なエスケープコードを定義する。例えば「カーソルを左に移動」はESC[D、「テキストを赤くする」はESC[31mです。仕様では「カーソル左」はCURSOR LEFTと呼ばれ、色変更のものはSELECT GRAPHIC RENDITIONと呼ばれます。

形式は拡張可能なので、将来的に他の人がさらに多くのエスケープコードを定義する余地があります。今日人気の多くのエスケープコードはECMA-48で定義されていません。例えば、ターミナルアプリケーション(vim、htop、tmuxなど)がマウスをサポートするのは一般的ですが、ECMA-48はマウス用のエスケープコードを定義していません。

xterm制御シーケンス

ECMA-48で定義されていないエスケープコードがいくつかあります。例えば:

  • マウスレポートの有効化(ターミナルのどこをクリックしたか?)
  • ブラケットペースト(そのテキストを貼り付けたのか入力したのか?)
  • OSC 52(ターミナルアプリケーションがシステムのクリップボードにテキストをコピーするために使用)

これらやその他いくつかはxterm由来で、XTerm Control Sequencesに文書化されており、他のターミナルエミュレータでも広く実装されていると私は考えています(誤りがあれば指摘してください!)。

この「xtermがサポートするもの」のリストは厳密には標準ではありませんが、xtermは非常に影響力が大きいため、重要な文書であると言えます。

terminfo

80年代(今日でもある程度ですが、私の理解では80年代の方がはるかに劇的でした)には、ターミナルが実際にサポートするエスケープコードに非常に大きなばらつきがありました。

これに対処するため、さまざまなターミナル用のエスケープコードのデータベース「terminfo」が存在します。

terminfoの標準はX/Open Cursesと呼ばれているようです(理由は不明ですが、閲覧するにはアカウント作成が必要です)。この標準はデータベース形式と、データベースにアクセスするためのCライブラリインターフェース(「curses」)を定義しています。

例えば、システムが認識しているすべての異なるターミナル用の「画面クリア」の可能なエスケープコードをすべて表示するには、以下のbashスニペットを実行できます:

for term in $(toe -a | awk '{print $1}')
do
  echo $term
  infocmp -1 -T "$term" 2>/dev/null | grep 'clear=' | sed 's/clear=//g;s/,//g'
done

私のシステム(およびこれまで使用したほぼすべてのシステム)では、terminfoデータベースはncursesによって管理されています。

プログラムはterminfoを使うべきか?

アプリケーションがANSIエスケープコードを扱う際に取る2つの主なアプローチがあるのは興味深いと思います:

  1. TERM環境変数に含まれる内容に応じて、使用するエスケープコードをterminfoデータベースから判断する。Fishはこの方法を採用しています。
  2. 「十分な」ターミナルエミュレータで動作するエスケープコードの「単一の共通セット」を特定し、それをハードコードする。

アプローチ#2(「terminfoを使わない」)を採用しているプログラム/ライブラリの例:

なぜ人々がterminfoから離れつつあるのか気になり、fishのメンテナーの一人による非常に興味深く詳細なterminfoに関するrantを見つけました。このrantでは次のように主張されています:

[terminfoの著者たち]は、当時非常に重要で役立つ多くの作業を行いました。私の言いたいのは、それがもはやそうではないということです。

要約すると不十分なので要約しませんが、読む価値があると思います。

エスケープコードの「単一の共通セット」は存在するのか?

先ほど、「ほとんどの人に動作するエスケープコードの『共通セット』を使う」というアイデアについて話しました。しかし、そのセットとは何でしょうか?合意はあるのでしょうか?

私はこの答えを全く知りませんが、いくつか読んだところでは、以下のような組み合わせのようです:

  • VT100がサポートしていたコード(ただし、一部は現代のターミナルでは関係ありません)
  • ECMA-48に含まれるもの(これももはや関係ないものがいくつかあると思います)
  • xtermがサポートするもの(ただし、すべてが実際に十分に広くサポートされているとは限らないと推測します)

そして最終的には、「ユーザーが最も頻繁に使うと思われるターミナルエミュレータを特定し、それらでテストする」という、Web開発者がどのCSS機能を使うかを決めるのと同じ方法になるのかもしれません。

WebのCan I use…?Baselineのようなリソースはターミナルには存在しないと思います。(理論上、terminfoはターミナルの「caniuse」であるはずですが、新しいターミナル機能が発明されてから追加されるまでに10年以上かかることが多く、非常に限定的です)

terminfoを使う理由

2025年に人々がterminfoを価値あるものだと感じる理由をMastodonで尋ねたところ、私には理にかなういくつかの理由が得られました:

  • TERM環境変数を使ってプログラムの動作を制御できることを期待する人がいる(例えばTERM=dumb)ため、terminfo以降の世界でそれがどのように機能すべきかの標準がない
  • ターミナルエミュレータ間のばらつきは80年代よりは少ないものの、ゼロからは程遠い:グラフィカルターミナル、Linuxフレームバッファコンソール、シリアルコンソールを介してサーバーに接続している状況、Emacsのシェルモードなど、私が見落としているものもおそらくある
  • エスケープコードの「単一の共通セット」が何であるかについての単一の標準は存在せず、時には実際に十分に広くサポートされていないエスケープコードをプログラムが使用することがある

terminfoとユーザーエージェント検出

ncursesが使用するエスケープコードを決定するためにTERM環境変数を使う方法は、Webサーバーがブラウザのユーザーエージェントを使って提供するWebサイトのバージョンを決定していた方法を思い起こさせます。

また、同じような結果をもたらしているようです — iTerm2が自身を「xterm-256color」として報告する方法は、Safariのユーザーエージェントが「Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.3 Safari/605.1.15」であるのと似ています。どちらの場合も、ターミナルエミュレータ/ブラウザはうまく機能していないユーザーエージェント検出を回避するためにユーザーエージェントを変更することになります。

Webでは、ユーザーエージェント検出は良いプラクティスではないと判断し、代わりに標準化に焦点を当てて、すべてのブラウザに同じHTML/CSSを提供できるようにしました。ターミナルでも同じアプローチが未来なのかどうかはわかりません — 現在のターミナル環境は、Webがこれまであったよりもはるかに断片化されており、資金もはるかに少ないと思います。

その他の文書/標準

エスケープコードに関連するその他の文書と標準を順不同でいくつか:

これが面白いと思う理由

Unixターミナルは「時代遅れ」だと言う人を時々見かけます。私はターミナルが大好きなので、どのような漸進的な変更がそれをより「時代遅れ」でなく感じさせるのか、いつも興味があります。

Webのように、より明確な標準の状況があれば、ターミナルエミュレータ開発者が新機能を構築しやすくなり、ターミナルアプリケーションの著者がそれらの機能をより自信を持って採用できるようになり、私たち全員がその恩恵を受け、ターミナルでの体験をより豊かなものにできるかもしれません。

明らかに、ANSIエスケープコードの標準化は簡単ではありません(ECMA-48が最初に公開されてからほぼ50年が経ちましたが、まだそこには到達していません!)。どのような課題があるのかすら私はすべて知りません。しかし、HTML/CSS/JSの状況も以前は非常に悪かったのに、今ははるかに良くなっているので、もしかしたら希望があるかもしれません。