Linux의 clear와 Mac의 clear는 다르다

최근 CSI Sequence에 대한 글을 열심히 쓰고 있지만, 우리가 직접 CSI Sequence를 직접 사용할 일은 거의 없다. 하지만 알게 모르게 사용하는 CSI sequence가 있다. 바로 화면을 지우는 데 사용되는 clear 라는 명령어다. clear 는 기본적으로 두 가지 종류의 CSI sequence를 사용한다. 하나는 CUrsor Position(a.k.a CUP; CSI H )다. 이것을 이용해 커서를 화면의 시작으로 돌린다. clear 를 하고 나면, 최상단 왼쪽으로 커서가 움직이는 것은 CUP 덕분이다. 두 번째 CSI sequence는 Erase in Display(a.k.a. ED; CSI 2 J )다. 이를 이용해 화면 전체를 지운다. 여기까지는 Linux와 Mac이 공유하는 동작이다. 하지만 이후 동작에서 Linux의 clear 와 Mac의 clear 는 다르게 동작한다. 간단하게 말하면 Linux의 clear 는 스크롤 버퍼를 지우고, Mac의 clear 는 지우지 않는다. 이는 Linux의 clear는 앞의 두 시퀀스에 이어 CSI 3 J 를 출력하기 때문이다. CSI 3 J 는 xterm 이 도입한 Escape Sequence의 확장으로 스크롤 버퍼에 저장된 라인을 지우는 시퀀스다. 2011년 레드 햇 진영에서 E3라고 이름붙인 뒤 로 터미널 에뮬레이터에서는 E3 확장이라고 부르기도 한다. Mac의 clear 는 이 시퀀스를 출력하지 않기 때문에 스크롤 버퍼를 남겨둔다. 이런 차이가 발생한 것은 생각보다 오래된 일은 아니다. 원래 대부분의 터미널 에뮬레이터들은 E3 확장을 비롯한 xterm 확장들을 이해하지 못했다. 당연히 이 시절에는 Linux에서 사용되던 clear 커맨드도 E3를 사용하지 않았기 때문에 스크롤 버퍼는 지우지 않았다. 하지만 시간이 지나며 점점 xterm 확장은 다른 터미널 에뮬레이터로 전해져오며 E3도 구현되기 시작했다. 2007년 PuTTY , 2011년 Red Hat , 2014년 Gnome

[CSI Sequence] 화면 지우기

오늘은 지난 글 에 이어서 CSI Sequence를 이용해 화면을 지우는 방법을 알아보도록 하겠다. CSI Sequence에는 화면을 지우는 시퀀스가 2개 있다. 첫 번째는 EL이라고 불리는 Erase in Line 시퀀스다. CSI # K 로 구성된 시퀀스로 이름 그대로 줄을 지우는 데 사용되는데, # 을 주지 않으면 기본값은 0으로 처리되며, 값을 준다면 0, 1, 2 총 세개중 하나를 줘야 한다. 이외의 값이 오는 경우 시퀀스 자체가 무시된다. 예를 들어 0x311b5b334b32 즉, 1^[3K2 같은 값을 출력하면 ^[3K 는 무시되고 화면에 12 만 출력된다. 0, 1, 2의 동작은 정리하면 다음과 같다. 0 커서부터 줄 끝까지 지운다. 1 줄 시작부터 커서까지 지운다. 2 커서의 위치와 상관 없이 줄 전체를 지운다. 주의할 점은 EL 시퀀스는 커서의 위치를 옮기지 않는다는 것이다. 즉, 지금 쓴 줄을 지우고 새로운 줄을 쓰고 싶으면, EL 시퀀스와 캐리지 리턴 (\r)을 같이 사용해줘야 한다. 두 번째는 ED라고 불리는 Erase in Display 시퀀스다. CSI # J 로 구성된 시퀀스로 이름 그대로 화면을 지우는 데, EL과 마찬가지로 # 의 값에 따라 화면의 어디를 지울지를 결정한다. 0 커서를 기준으로 화면 끝까지 지운다. 1 화면 시작부터 커서까지 지운다. 2 커서의 위치와 상관 없이 화면 전체를 지운다. 3 스크롤 버퍼에 저장된 라인을 지운다, 기본적으로 EL과 같은 느낌이다. 기본값이 0인 것도 같고, 커서의 위치를 옮기지 않는 것도 같고, 정해진 값 이외의 값이 들어오면 무시하는 것도 같다. 단 하나의 차이는 EL에 없는 3이 추가된 것이다. 이 시퀀스는 스크롤 버퍼에 저장된 값을 지우는 시퀀스다. 원래 VT100 시절에 있던 시퀀스는 아니다. 그 시절 터미널에는 스크롤이라는 개념이 없었다. 이 시퀀스는 X 윈도우 시스템 과

[CSI Sequence] 커서 옮기기

Code Abbr Name CSI # A CUU CUrsor Up CSI # B CUD CUrsor Down CSI # C CUF CUrsor Forward CSI # D CUB CUrsor Backward CSI # E CNL CUrsor Next Line CSI # F CPL CUrsor Previous Line CSI # I CHT Cursor Horizontal forward Tabulation CSI # Z CBT Cursor Backward Tabulation CSI # G CHA Cursor Horizontal Absolute CSI # ; # H CUP CUrsor Position 오늘은 지난 글에 이어 이번 글에서는 CSI sequence를 이용해 커서를 옮기는 방법에 대해 알아보겠다. 커서를 옮기는 CSI sequence의 종류는 위와 같이 정리할 수 있다. CUU, CUD, CUF, CUB 이들은 각각 CUrsor Up, CUrsor Down, CUrsor Forward, CUrsor Backward의 약자로 이름 그대로 커서를 위, 아래, 앞, 뒤로 이동한다. 인자로는 한 개의 숫자를 받고, 만약 인자가 생략되면 1로 취급된다. 즉, 0x1b[A 는 0x1b[1A 와 같은 의미이다. 이때 CUF나 CUB는 같은 줄 내에서만 움직이며 줄의 시작에서 CUB를 받아도 이전 줄로 움직이지 않고, 줄의 마지막에서 CUF를 받아도 다음 줄로 움직이지 않는다. 이런 동작을 원하면 각각 reverse auto-wrap 모드와 auto-wrap 모드를 켜야 하는데, 이 모드를 지원할지는 터미널별로 다르다. CNL, CPL 이들은 각각 Cursor Next Line, Cursor Previous Line을 의미하며, 커서를 다음 줄 혹은 윗줄로 옮긴다. CUU와 CUD는 커서의 column을 유지한 채 줄을 바꾼다면, CN

CSI Sequence의 구조

^[ [78G|/- ^[ [17;79H\_ ^[ [9;1H ^[ [1P ^[ [79G|( ^[ [11;79H/ ^[ [K 임베디드 프로그래밍을 해본적 있다면, 임베디드 장치의 시리얼 포트를 연결했을 때, 위와 같은 문자열이 전송돼는 것을 보았을 것이다. 여기서 ^[ 로 시작하는 문자열은 ESC ( \0x1b )를 나타내는 특수한 제어문자로, 이 정체불명의 문자열의 정체는 터미널을 제어하는 escape code를 의미한다. escape sequence 중에서 [로 시작하는 시퀀스를 control sequence 혹은 CSI sequence라고 부른다. control sequence를 시작하게 하는 ESC [ 를 CSI (Control Sequence Introducer라고 부르기 때문이다. CSI sequence는 CSI 를 시작으로 @ , ` , 혹은 알파뱃으로 끝나는 일련의 시퀀스로, CSI 와 마지막 문자 사이에 임의의 개수의 파라미터로 구성되는데, 이때 파라미터는 ASCII로 정의 된 0 ( 0x30 )- 9 ( 0x39 )로 표현되며, 파라미터 사이는 ; ( 0x59 )로 구분된다. 즉, 위의 예제에서 ^[ [78G 는 숫자 78을 파라미터로 받는 G로 끝나는 CSI sequence를 의미하고, ^[ [17;79H 는 숫자 17과 79를 파라미터로 받는 H로 끝나는 CSI sequence를 의미한다. 이번 글에서는 ESC(0x1b)로 시작하는 CSI sequence의 개념과 기본적인 구조에 대해 살펴보았다. CSI sequence는 터미널의 커서 위치, 색상, 속성 등을 제어한다. 이를 활용하여 터미널에서 다양한 동작을 수행하는 프로그램을 개발할 수 있다. 다음 글에서는 CSI sequence의 종류와 각각의 기능에 대해 자세히 설명해보겠다.

텍스트 애플리케이션에서 Carriage Return 사용하기

Unix를 비롯한 대부분의 시스템은 LF ( \n , 0x0A )를 개행 문자로 사용하기 때문에 CR ( \r , 0x0D )을 사용하는 경우가 거의 없다. 텍스트 애플리케이션에서 진행표시줄을 만드는 것이 진행표시줄을 만드는 것이 현대 컴퓨터에서 CR 을 사용하는 거의 유일한 경우라고 볼 수 있다. CR 을 사용하면 터미널에서 진행표시줄을 간단하게 구현할 수 있다.

터미널 출력 제어를 위한 termios 구조체 이해하기

지난 글 에서 설명했듯이 OS X, 리눅스를 포함한 Unix를 이어받은 운영 체제는 LF (line feed, 0x0A , \n )를 개행 문자, 즉 커서를 다음 줄의 시작으로 옮기는 문자로 이용한다. 하지만 표준에 정의 된 LF 의 동작은 커서를 다음 줄로 내리는 것일 뿐, 커서를 줄의 처음으로 이동하지 않는다. 파일을 항상 운영 체제에 종속적인 애플리케이션을 통해서만 접근한다면 표준과 다른 동작은 문제되지 않는다. 하지만 파일과 입출력의 구분이 없는 유닉스 계열에서 파일과 프로세스의 입출력이 상호작용할 때 이 차이는 문제될 수 있다. 이 차이를 다루기 위해서 터미널은 출력에 적절한 가공을 하여 출력한다. 이를 제어하기 위한 플래그가 POSIX.1 표준이 정의 하는 termios 구조체의 c_oflag 다. c_oflag 는 터미널이 받은 문자를 출력하기 전에 어떤 후처리를 할지에 대한 플래그다. c_oflag에서 가장 중요한 플래그는 OPOST 다. 이는 입력에 대한 후처리를 할지 말지에 대한 플래그로 OPOST 가 꺼져있으면 다른 플래그와 상관없이 터미널은 받은 문자열을 그대로 보여준다. 이 플래그를 끄는 경우는 거의 없다. 하지만 터미널을 텍스트를 보여주기 위한 용도가 아닌 바이너리 데이터를 전송하기 위해 사용하는 경우 끄는 것이 좋다. 터미널이 Unix 계열 운영 체제에서 원하는대로 동작할 수 있게 해주는 플래그는 ONLCR 이다. ONLCR 이 켜져 있으면 터미널은 출력을 해석할 때 NL 을 CRNL 로 해석한다. 즉, Unix에서도 ONLCR 이 꺼져있다면, LF 를 만났을 때, 다음 줄의 처음으로 이동하는 것이 아닌, 현재 위치의 다음 줄로 이동한다. Unix 계열 운영 체제에서 윈도우에서 만들어진 파일을 출력해야 할 경우, CRNL 을 NL 로 바꾸지 않고도 ONLCR 플래그를 끄는 것 만으로도 간단하게 출력할 수 있다. 이외에도 구형 Mac OS 처럼 동작하게 해주는 OCRNL 플래그나 탭문자( 0x09 , \t )를

LF, CR 그리고 CRLF

개행 문자는 멀티 플랫폼에서 작업하는 사람들을 귀찮게 하는 것 중 하나다. Mac OS, Windows, Linux는 모두 다른 문자를 개행 문자로 사용한다. 심지어 Mac OS는 과거 버전과 최신 버전에서의 동작이 또 다르다. 이번 글에서는 시스템별로 다른 개행 문자를 사용하는 원인을 살펴볼 것이다. ISO 6429 표준 에 따르면 LF (line feed, \n)는 커서를 현재 행을 유지하면서 다음 줄로 옮기고, CR (carriage return, \r)는 커서를 현재 라인의 처음으로 옮긴다. 우리가 원하는 개행 문자의 동작을 위해서는 CR 과 LF 를 함께 써야 한다. 이런 구분은 라인을 바꾸는 동작과, 커서를 처음으로 옮기는 동작이 구분된 초기 프린터나 타자기의 동작을 모방했기 때문이다. A B 즉, 표준에 따르면 " A\nB "라는 문자열은 위에 보인 예시대로 A 아래 바로 B 가 오는 것이 아니라, A 의 대각선 아래 B 가 와야 한다. 하지만 개행 문자로 CRLF 를 사용하는 시스템은 드물다. 지금은 스토리지가 매우 싼 자원이다. 하지만 과거에는 스토리지가 매우 비싼 자원이었다. 이 시절 시스템 설계자들은 개행 문자를 위해 2바이트나 할당하라는 것을 과도한 사치로 보았다. 결국 어떤 시스템 설계자는 LF 를 어떤 시스템 설계자는 CR 을 단독으로 개행 문자로 사용하기 시작했다. 그 와중에도 표준을 지키던 시스템이 있었다. 그중 하나는 CP/M 이라는 운영체계다. CP/M은 CR 과 LF 의 조합을 개행문자로 사용했다. 이는 단순히 표준을 지키고자 하는 의지 때문이 아니라, 과거 있었던 원격 터미널 장치와의 호환성을 유지하기 위한 전략적 선택이었다. 다른 말로 하면, 비싼 스토리지 비용을 감당하더라도 호환성을 유지해 시장을 장악하는 것이 유리하다는 판단이었다. 후에 나오는 몇몇 운영체제도 CP/M 과 같은 선택을 했고, 이 중 하나가 마이크로소프트의 MS-DOS였다. 이 선택이 지금까지 이어져 마이크로소프트의

이 블로그의 인기 게시물

[C++] enum class - 안전하고 쓰기 쉬운 enum

RAII는 무엇인가

Log Aggregator 비교 - Scribe, Flume, Fluentd, logstash

[Python] cache 데코레이터로 최적화하기

[Web] SpeechSynthesis - TTS API