Blog
개발중인 미완성 페이지로, 일부 기능이 동작하지 않을 수 있습니다.

\r\n(CRLF)와 \n\r의 차이

2024. 6. 7.|2024. 10. 6.

줄바꿈문자로 보통 윈도우는 \r\n을, POSIX류는 \n을 사용한다.
나는 운영체제 상관 없이 \n을 사용하지만.

\r\n\n은 무슨 뜻이고 차이는 뭘까? 그리고 왜 \n\r은 안 쓸까?

목차

각각의 뜻

\r은 캐리지 리턴(CR, Carrage Return)이고, \n이 진짜 줄바꿈문자(LF, Line Feed)이다.

재미도 감동도 없는 역사 얘기는 빼고, 유의미한 정보만을 전달하자면 대충 이 정도 되겠다.

  • \r(Carrage Return): 커서를 그 줄의 맨 앞으로 옮긴다
  • \n(Line Feed): 커서를 아래로 한 칸 내린다

대충 이런 의미지만 보통은 그냥 줄바꿈문자로 쓰인다.

그런데 정말 그 뜻 그대로 쓰이는 곳이 있다. 바로 CLI 환경이다.

예시

예시로 뭐가 어떻게 다른지 확인해보자.

줄바꿈 없을 때

#include <stdio.h>

int main() {
  return printf("Hello world!") != 12;
}

줄바꿈문자를 출력하지 않았을 때의 출력 결과는 아래 스크린샷과 같다. (sh 기준)

Result of printing nothing after line

프로그램 실행이 끝나고 $ 가 출력된 것이다.1

CR

#include <stdio.h>

int main() {
  return printf("Hello world!\r") != 13;
}

이 코드를 실행해보면 아래 스크린샷처럼, 커서가 그 줄의 맨 앞으로 이동된 것을 확인할 수 있다.

Result of printing CR after line

마찬가지로 프로그램 실행이 끝나고 $ 가 출력된 것인데,
커서가 맨 앞으로 이동돼 있었기 때문에 He부분이 가려져서 llo world!가 보이는 것이다.

LF

#include <stdio.h>

int main() {
  return printf("Hello world!\n") != 13;
}

Result of printing LF after line, canonical mode

뭔가... 이상하다?
커서가 아래로 한 칸 내려가기만 한 게 아니라 \r을 붙인 것 같은 결과가 나오고 있다.

이는 터미널의 모드 때문이다. 기본적으로는 canonical mode로, 자동으로 이것저것 처리된다.

raw mode로 들어가면 자동으로 이것저것 처리되던 것들이 꺼진다.
일단 출력 후처리만 끄고 해 보자.

#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>

static void enable_raw_mode();

int main() {
  enable_raw_mode();
  return printf("Hello world!\n") != 13;
}

static struct termios original_termios;

static void disable_raw_mode() {
  if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &original_termios) == -1)
    exit(EXIT_FAILURE);
}

static void enable_raw_mode() {
  if (tcgetattr(STDIN_FILENO, &original_termios) == -1)
    exit(EXIT_FAILURE);
  atexit(disable_raw_mode);
  struct termios raw = original_termios;
  raw.c_oflag &= ~OPOST;
  if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1)
    exit(EXIT_FAILURE);
}

Result of printing LF after line, raw mode

기대대로 나오는 것을 확인할 수 있다!

CRLF, LFCR

#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>

static void enable_raw_mode();

int main() {
  enable_raw_mode();
  if (printf("Hello world!\r\n") != 14)
    exit(EXIT_FAILURE);
  if (printf("Hello world!\n\r") != 14)
    exit(EXIT_FAILURE);
}

static struct termios original_termios;

static void disable_raw_mode() {
  if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &original_termios) == -1)
    exit(EXIT_FAILURE);
}

static void enable_raw_mode() {
  if (tcgetattr(STDIN_FILENO, &original_termios) == -1)
    exit(EXIT_FAILURE);
  atexit(disable_raw_mode);
  struct termios raw = original_termios;
  raw.c_oflag &= ~OPOST;
  if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1)
    exit(EXIT_FAILURE);
}

Result of printing CRLF or LFCR after line

CRLF나 LFCR이나 똑같아보인다. 그럼 왜 CRLF는 쓰고 LFCR은 안 쓰는 걸까?

그 이유는 바로 stdout의 버퍼링에 있다. 자세한 내용은 setvbuf 참조.

#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>

static void enable_raw_mode();

int main() {
  if (setvbuf(stdout, NULL, _IOLBF, 4096))
    exit(EXIT_FAILURE);
  enable_raw_mode();
  if (printf("Hello world!\r\n") != 14)
    exit(EXIT_FAILURE);
  sleep(10);
  if (printf("Hello world!\n\r") != 14)
    exit(EXIT_FAILURE);
  sleep(10);
}

static struct termios original_termios;

static void disable_raw_mode() {
  if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &original_termios) == -1)
    exit(EXIT_FAILURE);
}

static void enable_raw_mode() {
  if (tcgetattr(STDIN_FILENO, &original_termios) == -1)
    exit(EXIT_FAILURE);
  atexit(disable_raw_mode);
  struct termios raw = original_termios;
  raw.c_oflag &= ~OPOST;
  if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1)
    exit(EXIT_FAILURE);
}

Result of printing CRLF or LFCR after line, result

일단 결과는 같다.

Result of printing CRLF after line, sleep after CRLF

CRLF 이후에 sleep중인 모습. 예상과 같다.

Result of printing CRLF or LFCR after line, sleep after LFCR

LFCR 이후에 sleep중인 모습.
stdout이 줄 단위로 버퍼링되기 때문에 \r이 아직 출력되지 않은 상태.

결론

stdout의 라인 버퍼링 때문에 \n\r\r이 늦게 출력될 수 있으므로 \r\n을 쓴다.

Footnotes

  1. 쉘을 PS1='$ ' sh로 실행했음

CRLF
Terminal
setvbuf
termios
tcsetattr
tcgetattr
터미널
C

Comments