Standards for ANSI escape codes

Julia Evans

ANSIエスケープコードの標準

原文は Julia Evans により に公開されました。 このブログを購読する

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

長い間、ANSIエスケープコードについてはなんとなく知っている程度でした(「ターミナルで文字を赤くしたりするやつでしょ」くらいの認識)で、どこで定義されているのか、そもそも標準があるのかどうかもよくわかっていませんでした。なんとなく「ここから先は魔境」といった感覚を抱いていました。今年ターミナルについて学ぶなかで、わかったことがあります:

  1. ANSIエスケープコードは、ターミナルの使い勝手を大きく向上させています(リモートマシンにSSHで接続しているときでも、システムのクリップボードにコピーする方法があるのを知っていましたか?OSC 52というエスケープコードで実現できるんです!)
  2. 完全に標準化されているわけではないため、常に確実に動くとは限りません。しかも目に見えないものなので、エスケープコードにまつわる問題のトラブルシューティングはものすごく厄介です。

そこで、エスケープコードをめぐる標準にはどんなものがあるのか、自分用にまとめてみることにしました。エスケープコードがどうしても頼りなく厄介なものでなければならないのか、それとももっと安心して使える未来があるのか、知りたかったからです。

エスケープコードとは?

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

エスケープコードは、ターミナルエミュレータがターミナル上で動くプログラムとさまざまな情報(色やマウスの動きなど)をやり取りするための仕組みです。エスケープコードには大きく分けて2種類あります:

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

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

ECMA-48

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

ECMA-48は主に2つのことを定めています:

  1. エスケープコードの汎用的なフォーマットを定義すること(たとえばESC[に何かが続く「CSI」コードや、ESC]に何かが続く「OSC」コードなど)
  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

1980年代には(今でもある程度はそうですが、私の理解では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. 「十分な数の」ターミナルエミュレータで動く「共通の1セット」のエスケープコードを見極め、それをハードコードしてしまう方法。

アプローチ#2(「terminfoを使わない」)をとっているプログラムやライブラリの例としては、次のようなものがあります:

なぜ人々がterminfoから離れつつあるのか気になって調べてみたところ、fishのメンテナの一人による、非常に興味深く詳細なterminfoについての論考を見つけました。そこでは次のように主張されています:

[terminfoの作者たちは]当時、極めて重要で役に立つ多大な仕事を成し遂げました。私が言いたいのは、それがもはやそうではないということです。

私が要約しても十分に伝わらないと思うので、ここでは要約しません。ぜひ読んでみる価値があると思います。

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

さきほど、たいていの人には通用する「共通のセット」のエスケープコードが使えるという話をしました。では、そのセットとは具体的に何なのでしょうか?何らかの合意はあるのでしょうか?

正直なところ、私にはまったくわかりません。ただ、いろいろ読んでみた限りでは、次のようなものの組み合わせのようです:

  • VT100がサポートしていたコード(ただし、現代のターミナルではもはや関係のないものもあります)
  • ECMA-48に含まれているもの(こちらも、今では関係のないものが含まれていると思います)
  • xtermがサポートしているもの(ただし、そのすべてが十分に広くサポートされているわけではなさそうです)

そして結局のところ、「自分のユーザーが最もよく使いそうなターミナルエミュレータを特定して、そこでテストする」ということになるのかもしれません。Web開発者がどのCSS機能を使ってよいかを決めるときと同じようにです。

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

terminfoを使う理由

Mastodonでも、2025年現在なぜterminfoに価値を感じるのか尋ねてみたところ、納得できる理由がいくつか返ってきました:

  • プログラムの挙動をTERM環境変数で制御できることを期待している人もいます(たとえばTERM=dumbのように)。terminfo以降の世界でそれをどう実現すべきかという標準はありません
  • 80年代に比べてターミナルエミュレータ間のばらつきは減ったとはいえ、ゼロにはほど遠いです。グラフィカルなターミナル、Linuxのフレームバッファコンソール、サーバーのシリアルコンソール経由で接続している状況、Emacsのshellモードなど、私が把握しきれていないものも含めまだまだあります
  • 「共通の1セット」のエスケープコードが何であるかについて単一の標準がなく、実際には十分に広くサポートされていないエスケープコードをプログラムが使ってしまうこともあります

terminfoとユーザーエージェント判別

ncursesがTERM環境変数を使ってどのエスケープコードを使うかを決めるやり方は、かつて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を取り巻く状況もかつてはひどいものでしたが、今でははるかに良くなりました。だから、まだ希望はあるかもしれません。

この記事は「muse-spark-1.2-contributor」を使用して翻訳されました。

コメント