Standards for ANSI escape codes

Julia Evans

ANSI 이스케이프 코드 표준

안녕하세요! 오늘은 ANSI 이스케이프 코드에 대해 이야기해보려고 해요.

오랫동안 ANSI 이스케이프 코드는 어렴풋이 알고만 있었어요. (“터미널에서 글자를 빨갛게 만들고 뭐 그런 걸 하는 거” 정도로요.) 하지만 그게 정확히 어디에 정의되어 있는지, 표준이 있기는 한 건지에 대해서는 제대로 이해하고 있지 못했어요. 그냥 막연히 ‘여기서부턴 위험한 영역’ 같은 느낌이었죠. 올해 터미널에 대해 공부하면서 알게 된 건 다음과 같아요:

  1. ANSI 이스케이프 코드는 터미널의 사용성을 크게 개선하는 많은 기능들을 담당하고 있어요 (원격 머신에 SSH로 접속한 상태에서도 시스템 클립보드로 복사할 수 있는 방법이 있다는 거 알고 계셨나요?? OSC 52라는 이스케이프 코드 덕분이에요!)
  2. 완전히 표준화되어 있지 않아서 항상 믿고 쓸 수 있는 건 아니에요. 게다가 이스케이프 코드는 눈에 보이지 않기 때문에 문제가 생겼을 때 해결하기가 정말 답답해요.

그래서 이스케이프 코드와 관련된 표준에는 어떤 것들이 있는지 제 나름대로 목록을 정리해 보고 싶었어요. 이스케이프 코드가 원래부터 불안정하고 답답할 수밖에 없는 건지, 아니면 언젠가 우리가 좀 더 자신 있게 믿고 쓸 수 있는 미래가 올 수 있을지 알고 싶었거든요.

이스케이프 코드란 뭘까요?

터미널에서 왼쪽 화살표 키를 눌렀는데 ^[[D가 화면에 찍힌 걸 본 적 있나요? 그게 바로 이스케이프 코드예요! 첫 번째 문자가 ‘escape’ 문자이기 때문에 ‘이스케이프 코드’라고 불리는데, 보통 ESC, \x1b, \E, \033 또는 ^[로 표기해요.

이스케이프 코드는 터미널 에뮬레이터가 터미널에서 실행 중인 프로그램과 여러 종류의 정보(색상, 마우스 움직임 등)를 주고받는 방식이에요. 이스케이프 코드는 크게 두 가지가 있어요:

  1. 입력 코드는 유니코드로 표현할 수 없는 키 입력이나 마우스 움직임을 터미널 에뮬레이터가 보내는 코드예요. 예를 들어 ‘왼쪽 화살표 키’는 ESC[D, ‘Ctrl+왼쪽 화살표’는 ESC[1;5D일 수 있고, 마우스 클릭은 ESC[M :3 같은 형태일 수 있어요.
  2. 출력 코드는 프로그램이 텍스트에 색을 입히거나, 커서를 움직이거나, 화면을 지우거나, 커서를 숨기거나, 클립보드로 텍스트를 복사하거나, 마우스 리포팅을 활성화하거나, 창 제목을 설정하는 등 다양한 목적으로 출력할 수 있는 코드예요.

이제 표준에 대해 이야기해 볼게요!

ECMA-48

이스케이프 코드와 관련해 제가 처음 찾은 표준은 1976년에 처음 출간된 ECMA-48이었어요.

ECMA-48은 두 가지 역할을 해요:

  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

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 이스케이프 코드를 다루는 방식에는 크게 두 가지 접근법이 있다는 점이 흥미로웠어요:

  1. TERM 환경 변수에 들어 있는 값을 보고 어떤 이스케이프 코드를 써야 할지 terminfo 데이터베이스를 이용해 결정하는 방식이에요. 예를 들어 fish가 이렇게 해요.
  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처럼요). 그런데 post-terminfo 세상에서 그게 어떻게 동작해야 하는지에 대한 표준이 없어요.
  • 80년대보다는 터미널 에뮬레이터 간의 차이가 줄었지만 그렇다고 차이가 전혀 없는 건 아니에요. 그래픽 터미널, 리눅스 프레임버퍼 콘솔, 시리얼 콘솔로 서버에 접속했을 때의 상황, Emacs 셸 모드 등 제가 놓치고 있는 경우도 더 있을 거예요.
  • “단일 공통 집합”의 이스케이프 코드가 무엇인지에 대한 표준이 하나도 없고, 그래서 가끔 프로그램들이 실제로는 충분히 널리 지원되지 않는 이스케이프 코드를 사용하기도 해요.

terminfo와 User Agent 감지

ncurses가 TERM 환경 변수를 이용해 어떤 이스케이프 코드를 쓸지 결정하는 방식은, 예전에 웹 서버가 브라우저의 user agent를 보고 어떤 버전의 웹사이트를 보여줄지 결정하던 방식이 떠오르게 해요.

결과도 비슷하게 나타난 것 같아요. iTerm2가 자신을 “xterm-256color”라고 알리는 방식은 Safari의 user agent가 “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”인 것과 비슷하게 느껴져요. 두 경우 모두 터미널 에뮬레이터/브라우저가 제대로 동작하지 않는 user agent 감지를 우회하려고 user agent를 바꾸게 된 거죠.

웹에서는 결국 user agent 감지가 좋은 관행이 아니라는 결론을 내리고, 대신 표준화에 집중해서 모든 브라우저에 동일한 HTML/CSS를 제공할 수 있게 되었어요. 하지만 터미널에서도 같은 접근법이 미래가 될지는 모르겠어요. 오늘날 터미널 환경은 웹이 그랬던 때보다 훨씬 더 파편화되어 있고, 자금 지원도 훨씬 부족하다고 생각하거든요.

그 밖의 문서/표준들

이스케이프 코드와 관련된 몇 가지 문서와 표준을 더 소개할게요. 순서는 따로 없어요:

제가 이 주제를 흥미롭게 생각하는 이유

가끔 사람들이 유닉스 터미널이 “구식”이라고 말하는 걸 보곤 하는데, 저는 터미널을 정말 좋아해서 어떤 점진적인 변화가 터미널을 덜 “구식”으로 느끼게 만들 수 있을지 항상 궁금해요.

웹에서처럼 표준 환경이 더 명확하다면, 터미널 에뮬레이터 개발자가 새로운 기능을 더 쉽게 만들고 터미널 애플리케이션 개발자도 그 기능을 더 자신 있게 도입할 수 있어서, 우리 모두가 혜택을 받고 터미널에서 더 풍부한 경험을 할 수 있지 않을까요.

물론 ANSI 이스케이프 코드를 표준화하는 건 결코 쉽지 않아요 (ECMA-48이 처음 출간된 지 거의 50년이 지났는데도 아직 이루지 못했으니까요!). 어떤 어려움이 다 있는지도 저는 잘 모르겠어요. 하지만 HTML/CSS/JS 상황도 예전에는 정말 엉망이었는데 지금은 훨씬 나아졌잖아요. 그러니 희망은 있을지도 몰라요.

원문은 Julia Evans님이 에 게재했습니다.

이 글은 muse-spark-1.2-contributor 모델을 사용해 번역했습니다.