Standards for ANSI escape codes

Julia Evans

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

こんにちは!今日は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. エスケープコードの汎用的な形式を定義すること(たとえば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

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についての考察を見つけました。そこでは次のように主張されています。

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

私が要約してもうまく伝えられないと思うので、ここでは要約しません。ぜひ一読する価値があると思います。

「共通のエスケープコード一式」は存在する?

さきほど、多くの人にとって動作する「共通のエスケープコード一式」を使えるという考えについて触れました。では、その一式とは何なのでしょうか?何らかの合意はあるのでしょうか?

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

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

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

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

terminfoを使う理由

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

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

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

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

その結果も似たようなものになっているように感じます。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」となっていることと似ています。どちらの場合も、うまく機能していないユーザーエージェント判別を回避するために、ターミナルエミュレータ/ブラウザ側がユーザーエージェントを偽装することになっています。

ウェブでは最終的に、ユーザーエージェント判別は良い手法ではないと判断し、代わりに標準化に注力することで、すべてのブラウザに同じHTML/CSSを提供できるようにしました。ターミナルでも同じアプローチが未来になるのかどうかはわかりません。今日のターミナルの状況は、ウェブがかつてそうだった頃よりもはるかに分断されており、資金面でもはるかに乏しいと思います。

その他の文書・標準

エスケープコードに関連するその他の文書や標準を、順不同でいくつか挙げておきます。

なぜこれが面白いと思うのか

時々、UNIXターミナルは「時代遅れだ」という意見を目にします。私はターミナルが大好きなので、どんな小さな改善で「時代遅れ」感が薄れるのか、常に気になっています。

もし(ウェブのように!)もっと明確な標準の全体像があれば、ターミナルエミュレータの開発者が新機能を作りやすくなり、ターミナルアプリケーションの作者もより自信を持ってそれらの機能を取り入れられるようになるでしょう。そうなれば私たちみんなが恩恵を受け、ターミナルでの体験がより豊かになると思います。

もちろん、ANSIエスケープコードを標準化するのは簡単ではありません(ECMA-48が最初に公開されたのは約50年前ですが、いまだに道半ばです!)。どんな課題があるのか、私自身すべてを把握しているわけでもありません。でも、HTML/CSS/JSを取り巻く状況もかつてはひどいものでしたが、今ではずっと良くなりました。だから、希望はあるかもしれません。

原文は Julia Evans により に公開されました。

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