Windows의 문자 인코딩과 줄바꿈 - 문자 깨짐과 CRLF/LF의 기본

· 업데이트: · · Windows, 문자 인코딩, 문자 깨짐, 줄바꿈, UTF-8, CP932, PowerShell, Unicode

수정 이력(6건, 최종 수정 2026년 09월 03일)

이 글에 적용한 변경 사항의 기록입니다. 보관해 둔 수정 전 버전은 DOI가 부여된 고정 URL에서 읽을 수 있습니다.

permalink·저자 표기·지식 맵 래퍼·깨진 내부 링크 등 CI가 지적한 표시용 수정을 반영했습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
글 앞부분에 「이 글의 지식 맵」 절을 추가했습니다. 본문에서 다루는 개념과 그 관계를 요약, 그림, 상세 페이지 링크로 정리한 것입니다. 본문의 주장은 바꾸지 않았습니다.
같은 바이트열이 읽는 전제에 따라 어떻게 달라지는지 보여주는 그림과, 표시 깨짐과 데이터 손상의 갈림길을 보여주는 그림을 추가했습니다. 본문 설명은 바꾸지 않았습니다.
외부 리뷰(1283건)에 맞춰 본문을 갱신했습니다. 개별 변경 내용은 아래 이력을 참조하세요.
대상 독자와 전제 환경을 앞부분에 명시하고, BOM이 무엇이며 UTF-8에도 붙는 이유 절을 추가했습니다. 더불어 VS Code, 메모장, PowerShell 각각에서 문자 인코딩을 확인하고 설정하는 방법을 정리했습니다.
일본어 문장 안에 file, editor, console, bytes, code page 같은 영어 단어가 그대로 섞여 있던 것을, 통째로 일본어(파일, 에디터, 콘솔, 바이트열, 코드 페이지)로 고쳤습니다. 설명하는 내용 자체는 바꾸지 않았습니다. 더불어 관련 기사 링크 문구를 링크 대상의 현재 제목에 맞췄습니다.
최초 공개
이 글을 인용하기(DOI: 10.5281/zenodo.21635266)

이 글은 Zenodo에 보관되어 있습니다. 항상 최신 버전으로 연결되는 DOI와 지금 보고 있는 버전에 고정된 DOI를 아래에 함께 제시합니다.

Go Komura (2026). 「Windows의 문자 인코딩과 줄바꿈 - 문자 깨짐과 CRLF/LF의 기본」. 합동회사 코무라소프트. https://doi.org/10.5281/zenodo.21635266 https://comcomponent.com/ko/blog/windows-text-encoding-line-endings/

DOI(최신 버전)
10.5281/zenodo.21635266
DOI(이 버전)
10.5281/zenodo.21635267

Windows의 텍스트 관련 상담에서는, 꽤 자주 다음 이야기가 한꺼번에 섞입니다.

  • Shift_JISUTF-8은 무엇이 다른가
  • 왜 문자 깨짐이 일어나는가
  • CRLFLF는 무엇이 다른가
  • UTF-8로 바꿨는데도 왜 읽히지 않는 경우가 있는가
  • 왜 같은 파일인데 편집기, 콘솔, Excel, Git에서 보이는 방식이 달라지는가

이 문제는 일본어가 어려워서 생기는 것이 아닙니다. 거의 대부분은 같은 바이트열을 다른 전제로 읽었거나, 잘못 읽은 내용을 그대로 저장했기 때문입니다.

게다가 Windows에서는 Unicode 세계와 code page 세계가 지금도 공존합니다. 거기에 BOM, 줄바꿈, 편집기의 자동 판정, 콘솔의 code page, Git의 줄바꿈 변환까지 겹치므로 이야기가 복잡해 보입니다.

이 글에서는 Windows에서 자주 섞이는 Shift_JIS / UTF-8 / UTF-16, 왜 문자 깨짐이 일어나는지, CRLFLF의 차이, 그리고 왜 이 이야기가 혼란스러워 보이는지를 실무 기준으로 정리합니다.

내용은 2026년 4월 시점의 Microsoft Learn, PowerShell, Git, W3C / Unicode 계열 공개 정보를 전제로 합니다. 자세한 내용은 글 끝의 참고 자료를 참조하세요.

대상 독자·전제 환경

항목 내용
대상 독자 Windows에서 텍스트 파일(CSV, 로그, 설정 파일, 소스 코드)을 다른 부서·다른 시스템·Linux 측과 주고받는 개발자, 정보시스템 담당
전제 지식 특별히 없습니다. 바이트열과 문자 인코딩의 구분이 안 된 상태부터 읽을 수 있게 구성했습니다
전제 환경 Windows 10 / Windows 11. PowerShell은 Windows PowerShell 5.1과 PowerShell 7 둘 다 다룹니다
다루지 않는 것 개별 라이브러리별 문자 인코딩 변환 API 사용법, 폰트 / 글리프 설계, 전각·반각 같은 정규화

급할 때의 읽기 순서는 다음과 같습니다. 전체 그림만 필요하면 1장과 2장, 원인만 가려내려면 3장과 8장, 운영 규칙을 정하려면 7장부터 읽으세요.

목차

  1. 먼저 잡아 둘 점
  2. 용어를 나눠 본다
    • 2.1 Unicode / UTF-8 / UTF-16 / CP932는 무엇이 다른가
    • 2.2 Shift_JISCP932를 어떻게 볼 것인가
    • 2.3 ANSI, Unicode, UTF-8N이라는 말의 함정
    • 2.4 BOM이란 무엇인가, 왜 UTF-8에도 붙는가
  3. 왜 문자 깨짐이 일어나는가
    • 3.1 문자 깨짐의 정체
    • 3.2 표시 깨짐과 데이터 손상은 다르다
    • 3.3 표현할 수 없는 문자로 떨어뜨리면 되돌릴 수 없다
  4. 줄바꿈 문자의 차이란
    • 4.1 CRLF / LF / CR
    • 4.2 줄바꿈은 문자 인코딩과는 별개 문제
    • 4.3 \n과 파일상의 줄바꿈 바이트열은 같다고 할 수 없다
  5. 왜 Windows에서는 특히 혼란스러운가
    • 5.1 Unicode와 레거시 code page가 공존한다
    • 5.2 라벨이 맞춰져 있지 않다
    • 5.3 ASCII만 있으면 문제가 숨는다
    • 5.4 파일 내용, 파일 이름, 콘솔, 소스 파일은 다른 계층이다
    • 5.5 BOM과 줄바꿈도 별도 축으로 작용한다
    • 5.6 도구가 제멋대로 바꾼다
  6. 흔한 사고 패턴
  7. 실무에서 사고를 줄이는 규칙
  8. 문자 깨짐·줄 끝 차이는 이 5가지 질문으로 조사한다
  9. 정리
  10. 관련 글
  11. 참고 자료

이 글의 지식 맵

Windows 텍스트는 바이트열·문자 인코딩·개행 코드·BOM이라는 서로 다른 축의 조합으로 이루어지며, UTF-8과 UTF-16LE는 Unicode 코드 포인트를 바이트열로 만드는 인코딩인 반면 CP932는 일본어 Windows 고유의 레거시 코드 페이지라서 전제를 뒤바꾸면 문자가 깨집니다. BOM은 UTF-16의 바이트 순서를 나타내는 표식인 동시에 UTF-8에서는 「UTF-8이다」라는 시그니처로 쓰이며, Windows PowerShell 5.1은 비ASCII 문자가 포함된 스크립트를 올바르게 읽기 위해 BOM이 필요합니다. 개행 코드는 CRLF와 LF 중 하나이며 문자 인코딩과는 별개 문제입니다. 리포지토리 단위의 처리는 .gitattributes로 고정할 수 있지만, Git은 개행은 변환해도 문자 인코딩은 자동으로 고쳐 주지 않습니다. 문자 깨짐은 올바른 인코딩으로 다시 열면 돌아오는 경우가 많지만, 잘못 읽은 내용을 그대로 저장하면 원래 바이트열이 사라져 표시가 깨진 것이 아니라 되돌릴 수 없는 데이터 손상이 됩니다.

Windows의 문자 인코딩과 개행 코드의 지식 맵Unicode와 UTF-8·UTF-16LE·CP932의 관계, BOM의 역할, 문자 깨짐과 데이터 손상의 차이, PowerShell의 기본값과 Git의 개행 변환이 개행 코드와 문자 인코딩 각각에 어떻게 작용하는지를 보여주는 그림.이용한다이용한다이용한다이용한다이용한다이용한다원인이 될 수 있다구현을 담당한다구현을 담당한다양립하지 않는다원인이 될 수 있다양립하지 않는다양립하지 않는다이용한다이용한다이용한다전제로 한다에서 구성할 수 있다원인이 될 수 있다문자 코드(인코딩)줄바꿈 코드UnicodeUTF-8UTF-16LECP932활성 코드 페이지(ANSI/ACP)BOM(Byte Order Mark)문자 깨짐PowerShell 문자 코드 기본값.gitattributes오독 내용 재저장에 의한 데이터 손상

그림의 실선은 항상 성립하는 관계, 점선은 조건이 붙는 관계입니다(성립 조건은 상세 페이지의 관계별 설명에 적혀 있습니다). 관계 전체 목록(총 19건, 근거와 확신도 포함)과 주요 개념의 정의는 지식 맵 상세 페이지에 정리되어 있습니다(일본어). 데이터: JSON-LD / Turtle

1. 먼저 잡아 둘 점

먼저 결론만 나열하면, 중요한 것은 다음 7가지입니다.

  • 텍스트 파일은 문자열 그 자체가 아니라 바이트열 + 문자 인코딩 + 줄바꿈으로 이루어집니다. 경우에 따라서는 BOM(Byte Order Mark, 바이트 순서 표시)도 붙습니다.
  • 문자 깨짐은 같은 바이트열을 다른 문자 인코딩으로 디코딩했을 때 일어납니다.
  • 줄바꿈 문제는 문자 인코딩이 맞아도 줄 구분 전제가 어긋날 때 일어납니다.
  • UnicodeUTF-8은 같은 뜻이 아닙니다. Unicode는 문자 집합 쪽 이야기이고, UTF-8이나 UTF-16은 그것을 바이트열로 만드는 문자 인코딩 이야기입니다.
  • Windows에서 「Shift_JIS」라고 불리는 것은, 실무에서는 CP932 / Windows 계열의 일본어 code page로 의식하는 편이 대화가 덜 흔들립니다.
  • UTF-8로 했다만으로는 아직 사양으로 부족합니다. BOM 유무줄바꿈까지 정해야 비로소 운영 규칙이 됩니다.
  • 혼란의 근원은 일본어 자체가 아니라, 역사가 다른 여러 전제가 같은 Windows 위에 남아 있는 것입니다.

Windows 텍스트를 다룰 때는, 먼저 다음 네 가지를 나눠 생각하는 것이 출발점입니다.

  1. 그 파일의 바이트열은 무엇인가
  2. 어느 문자 인코딩으로 쓰였는가
  3. 어느 문자 인코딩으로 읽혔는가
  4. 줄바꿈은 CRLF / LF 중 어느 쪽인가

여기가 갈라지는 것만으로도 꽤 헤매기 어려워집니다.

2. 용어를 나눠 본다

2.1 Unicode / UTF-8 / UTF-16 / CP932는 무엇이 다른가

먼저 말을 한 번 나눠 보는 편이 빠릅니다.

용어 무엇을 가리키는가 흔한 혼동
Unicode 문자를 번호로 나타내기 위한 틀 U+3042 () UTF-8과 같은 것이라고 생각한다
UTF-8 Unicode를 바이트열로 만드는 문자 인코딩 E3 81 82 Unicode 자체라고 생각한다
UTF-16LE Unicode를 바이트열로 만드는 문자 인코딩 42 30 메뉴상의 Unicode 표기와 섞인다
CP932 Windows 일본어 계열의 레거시 code page 82 A0 Shift_JIS와 완전히 같다고 생각한다
CRLF / LF 줄 구분의 바이트열 0D 0A / 0A 문자 인코딩의 일종이라고 생각한다
BOM (Byte Order Mark) 파일 맨 앞의 식별용 바이트열 EF BB BF 문자 인코딩 이름 그 자체라고 생각한다

예를 들어 라는 한 글자라도, 바이트열은 문자 인코딩마다 다릅니다.

문자: あ

UTF-8    : E3 81 82
CP932    : 82 A0
UTF-16LE : 42 30

중요한 점은 문자와 바이트열은 별개라는 것입니다. 앱은 화면에서는 「문자」로 보이는 것을 다루지만, 저장이나 통신에서는 최종적으로 바이트열을 주고받습니다. 사고는 대개 그 변환 경계에서 일어납니다.

2.2 Shift_JISCP932를 어떻게 볼 것인가

현장에서는 일본어 Windows의 텍스트 파일을 묶어 「Shift_JIS」라고 부르는 경우가 많습니다. 대화로는 통하지만, 실무에서는 조금 거칠습니다.

Windows의 일본어 레거시 텍스트를 조금 더 정확히 의식하려면 CP932, 또는 Windows 계열의 일본어 code page로 생각하는 편이 안전합니다.

여기를 대충 넘기면 다음과 같은 대화가 흔들립니다.

  • Shift_JIS로 저장해라고 말했는데, 상대는 Windows 측 CP932를 상정하고 있었다
  • Linux / macOS 측에서는 shift_jis로 다뤘지만, Windows에서 온 파일의 일부에서 재현이 맞지 않는다
  • ANSI 저장이라고 들었는데, 어느 code page인지는 환경에 따라 달랐다

그래서 사양이나 조사 메모에서는 되도록 다음과 같이 쓰는 편이 안전합니다.

  • Shift_JIS가 아니라 CP932
  • ANSI가 아니라 ACP (active code page) / 일본어 환경에서는 보통 CP932
  • 텍스트가 아니라 UTF-8 no BOM, LF처럼 구체적으로 적는다

2.3 ANSI, Unicode, UTF-8N이라는 말의 함정

Windows 주변에서는 말의 라벨도 혼란의 원인입니다.

특히 헷갈리기 쉬운 것은 다음 세 가지입니다.

  • ANSI Windows UI나 오래된 설명에 나오지만, ASCII가 아닙니다. 많은 경우 그 머신의 active code page(ACP) 를 가리킵니다.
  • Unicode 일부 편집기나 도구에서는 메뉴상의 UnicodeUTF-16LE를 의미하는 경우가 있습니다. Unicode로 저장했다고 해도 UTF-8이라고는 할 수 없습니다.
  • UTF-8N 일본어권 편집기에서 보이기도 하지만, 이것은 보통 UTF-8 no BOM을 구별하기 위한 UI 라벨입니다. 문자 인코딩의 정식 명칭이 아닙니다.

같은 말이라도 도구마다 뜻이 어긋나는 것이 Windows입니다. 여기가 첫 번째 큰 혼란 지점입니다.

2.4 BOM이란 무엇인가, 왜 UTF-8에도 붙는가

BOM은 Byte Order Mark(바이트 순서 표시)의 약자로, 파일이나 스트림 맨 앞에 두는 U+FEFF라는 코드 포인트입니다. 이름 그대로, 본래 역할은 바이트가 늘어선 순서를 나타내는 것입니다.

UTF-16이나 UTF-32는 2바이트 / 4바이트를 한 덩어리로 문자를 나타내므로, 그 덩어리를 「하위 바이트부터 늘어놓을지(little-endian)」「상위 바이트부터 늘어놓을지(big-endian)」를 읽는 쪽에 알려야 합니다. 그래서 맨 앞에 BOM을 두고, 그 BOM의 바이트 순서로 이후에도 같은 순서임을 나타냅니다.

맨 앞 바이트열 의미
FF FE UTF-16 little-endian
FE FF UTF-16 big-endian
EF BB BF UTF-8

여기서 생기는 의문은, UTF-8은 1바이트 단위라 순서 문제가 없을 텐데 왜 BOM이 붙는가입니다.

이유는 UTF-8의 BOM이 바이트 순서가 아니라 「이 파일은 UTF-8입니다」라는 표식(시그니처) 으로 쓰이기 때문입니다. UTF-8은 ASCII와 호환되므로, 내용이 영숫자만이면 레거시 code page와 구분이 안 됩니다. 맨 앞에 EF BB BF를 두면 읽는 쪽은 「active code page가 아니라 UTF-8로 읽으면 된다」고 판단할 수 있습니다.

뒤집으면 다음과 같습니다.

  • BOM을 붙이는 이점: 읽는 쪽이 문자 인코딩을 잘못 추측하기 어려워집니다. 특히 Windows PowerShell 5.1은 BOM 없는 스크립트 파일을 active code page로 읽으므로, 비 ASCII 문자가 들어 있는 스크립트는 BOM이 없으면 깨집니다.
  • BOM을 붙이는 단점: 맨 앞에 3바이트의 여분 데이터가 늘어납니다. BOM을 모르는 구현에서는 첫 줄 맨 앞에 보이지 않는 문자가 섞인 것처럼 다룹니다. CSV 헤더의 1열만 이름이 맞지 않는다, 셸 스크립트의 #! 행이 인식되지 않는다, 같은 사고는 여기서 납니다.

즉 UTF-8의 BOM은 「붙이는 것이 맞다 / 틀리다」가 아니라, 누가 읽는가에 따라 정하는 설정 항목입니다. 그래서 7.1처럼 「UTF-8로 한다」만이 아니라 「with BOM인지 no BOM인지」까지 정해야 합니다.

3. 왜 문자 깨짐이 일어나는가

3.1 문자 깨짐의 정체

문자 깨짐의 정체는 꽤 단순합니다.

  1. 문자열을 어떤 문자 인코딩으로 바이트열로 만든다
  2. 그 바이트열을 다른 문자 인코딩으로 문자열로 되돌린다
  3. 전제가 맞지 않으면 다른 문자열이 된다

예를 들어 를 UTF-8로 저장하면 바이트열은 이렇게 됩니다.

E3 81 82

이것을 UTF-8로 읽으면 이지만, CP932 전제로 읽으면 縺� 같은 다른 문자열로 보입니다. 이때 깨진 것은 「일본어」가 아니라 디코딩 전제입니다.

문자 깨짐을 한 줄로 말하면 이렇습니다.

같은 바이트열을, 다른 문자 인코딩으로 읽었다.

UTF-8로 인코딩해서 저장UTF-8로 디코딩CP932로 디코딩문자 あ바이트열 E3 81 82파일에 들어 있는 것은 이것뿐문자 あ ── 전제가 일치한다縺 같은 다른 문자열 ── 전제가 어긋난다

그림1: 두 갈래에서도 파일 내용(바이트열)은 같다. 다른 것은 읽는 쪽이 둔 전제뿐이다

3.2 표시 깨짐과 데이터 손상은 다르다

여기서 중요한 것은 아직 되돌릴 수 있는 단계이미 되돌리기 어려운 단계를 나누는 것입니다.

예를 들어 다음 흐름이라면 아직 되돌릴 여지가 있습니다.

  1. UTF-8 파일을 CP932로 연다
  2. 화면에는 縺�처럼 보인다
  3. 아직 저장하지 않았다

이 단계에서는 원래 바이트열이 UTF-8인 채로 남아 있습니다. 올바른 문자 인코딩으로 다시 열면 돌아오는 경우가 있습니다.

위험한 것은 다음 흐름입니다.

  1. UTF-8 파일을 CP932로 잘못 읽는다
  2. 깨져 보이는 내용을 그대로 저장한다
  3. 원래 UTF-8 바이트열이 사라진다

여기까지 가면 표시 깨짐이 아니라 데이터 손상입니다.

CP932 전제로 연다다시 연다그대로 저장한다UTF-8로 저장된 파일바이트열은 E3 81 82화면에는 縺처럼 보인다= 표시 깨짐. 바이트열은 아직 UTF-8올바른 문자 인코딩을 지정하면 원래대로 돌아온다잘못 읽은 문자열을 CP932로 다시 쓴다= 데이터 손상. 원래 바이트열이 사라진다나중에 올바른 문자 인코딩을 알아도 되돌릴 수 없다

그림2: 갈림길은 「다시 열었는가, 저장했는가」 한 수뿐이다. 조사에서는 반드시 여기를 먼저 확인한다

실무에서는 「문자가 깨졌다」는 한 문장으로 묶지 말고, 적어도 다음 둘을 나누는 것이 중요합니다.

  • 바이트열 자체는 아직 올바른가
  • 잘못 읽은 내용이 이미 다시 저장되었는가

3.3 표현할 수 없는 문자로 떨어뜨리면 되돌릴 수 없다

또 하나 위험한 것은 Unicode 문자열을 CP932처럼 좁은 code page로 떨어뜨릴 때입니다.

이때 상대 쪽에 없는 문자가 들어 있으면 다음 중 하나가 일어납니다.

  • ?로 바뀐다
  • 치환 문자가 들어간다
  • 변환 오류가 난다
  • 비슷한 다른 문자로 맞춰진다

예를 들어 일부 이모지나 확장 한자는 CP932로 그대로 떨어뜨릴 수 없습니다. 이 사고는 「읽을 수 있는가」가 아니라 왕복 변환해서 원래로 돌아오는가로 봐야 합니다.

한 번 사라진 정보는 나중에 올바른 문자 인코딩을 알아도 원래로 돌아오지 않습니다.

4. 줄바꿈 문자의 차이란

4.1 CRLF / LF / CR

줄바꿈도 바이트열입니다.

  • CR = carriage return = 0D
  • LF = line feed = 0A
  • Windows 텍스트 파일에서는 CRLF (0D 0A)가 전통적
  • Linux / Unix 계열에서는 LF (0A)가 일반적
  • CR 단독은 구 Mac 계열 등 오래된 맥락에서 나오기도 한다

표로 보면 이렇습니다.

줄바꿈 바이트열 주요 맥락
CRLF 0D 0A Windows의 기존 텍스트 파일, 레거시 도구
LF 0A Linux / macOS / 많은 개발 도구
CR 0D 꽤 오래된 레거시 데이터

4.2 줄바꿈은 문자 인코딩과는 별개 문제

여기가 꽤 중요합니다.

줄바꿈은 문자 인코딩과는 별개 문제입니다.

같은 UTF-8 파일이라도 줄바꿈은 CRLF일 수도 LF일 수도 있습니다. 예를 들어 A, 줄바꿈, B라는 내용이면 바이트열은 다음처럼 달라집니다.

UTF-8 + LF   : 41 0A 42
UTF-8 + CRLF : 41 0D 0A 42

즉,

  • UTF-8인데 줄바꿈만 다르다
  • CP932인데 줄바꿈은 LF
  • UTF-16LE인데 줄바꿈은 CRLF

는 흔히 있을 수 있습니다.

그래서 UTF-8로 바꿨는데도 아직 다르다는 때, 실제로는 문자 인코딩이 아니라 줄바꿈만 어긋난 경우가 있습니다.

4.3 \n과 파일상의 줄바꿈 바이트열은 같다고 할 수 없다

프로그래머 관점에서 은근히 헷갈리기 쉬운 지점입니다.

소스 코드에서 \n이라고 썼다고 해서 파일에 반드시 0A만 나온다고 할 수 없습니다. 언어나 런타임, I/O API의 텍스트 모드에서는 Windows에서 \nCRLF로 변환되는 경우가 있습니다.

즉 다음이 어긋날 수 있습니다.

  • 소스 코드상의 줄바꿈 표현
  • 실행 시 문자열
  • 파일에 저장된 바이트열
  • 편집기에서 보이는 줄바꿈

이 때문에 「나는 LF를 쓴 줄 알았는데 파일은 CRLF였다」는 사고가 납니다.

최근 편집기는 LF 단독을 평범하게 다루지만, 주변 도구, 레거시 앱, 업무 운영에는 아직 CRLF 전제가 남아 있습니다. 그래서 줄바꿈 문제는 「옛날 이야기」가 아니라, 지금도 실무에서 흔히 나옵니다.

5. 왜 Windows에서는 특히 혼란스러운가

5.1 Unicode와 레거시 code page가 공존한다

Windows가 복잡한 가장 큰 이유는 이것입니다.

Windows에는

  • Unicode를 쓰는 길
  • code page 기반으로 다루는 길

둘 다 남아 있습니다.

비교적 새로운 앱, Web, 크로스 플랫폼 자산은 UTF-8로 기울기 쉬운 반면, 오래된 CSV, TXT, 로그, Excel 주변, 업무 시스템 연동에는 CP932가 남아 있습니다. 게다가 일부 출력이나 API 주변에서는 UTF-16LE도 흔히 나옵니다.

한 대의 Windows 안에 여러 텍스트 문화가 같이 살고 있는 것입니다.

5.2 라벨이 맞춰져 있지 않다

혼란을 키우는 것은 기술 자체보다 라벨의 어긋남입니다.

  • Shift_JIS라고 하지만 실체는 CP932
  • ANSI라고 하지만 실체는 active code page
  • Unicode라고 하지만 실체는 UTF-16LE
  • UTF-8이라고 하지만 실제로는 BOM 있음 / 없음이 미정
  • UTF-8N 같은 편집기 고유 라벨이 나온다

여기를 모호하게 두면 대화는 통하는 것처럼 보여도 실체가 맞춰져 있지 않습니다.

5.3 ASCII만 있으면 문제가 숨는다

이것도 큽니다.

UTF-8은 ASCII 범위와 호환되므로, 영숫자와 기호만 있는 파일은 전제가 틀려도 「그럭저럭 읽히는」 경우가 있습니다. CP932 쪽도 ASCII에 해당하는 범위는 겉보기가 잘 안 깨지므로 문제가 겉으로 나오지 않습니다.

그 결과 이런 상태가 됩니다.

  • 영어만 있는 설정 파일은 문제없어 보인다
  • 일본어를 한 줄 넣는 순간 깨진다
  • 오래 숨어 있던 문제가 운영 중에 처음 터진다

그래서 문자 인코딩 사고는 「어제까지 되던 것이 오늘 갑자기 깨진」 것처럼 보이기 쉽습니다. 실제로는 전부터 지뢰가 있었고, 비 ASCII 문자가 들어간 순간에 보인 것인 경우가 많습니다.

5.4 파일 내용, 파일 이름, 콘솔, 소스 파일은 다른 계층이다

Windows에서는 다음을 한데 묶어 「문자 인코딩」이라고 부르면 헤맵니다.

  • 파일 이름 / 경로
  • 파일 내용
  • 콘솔 표시
  • 소스 코드 파일 자체의 문자 인코딩
  • 실행 시 문자열 형식
  • 클립보드나 GUI 부품상의 표시

예를 들어 일본어 파일 이름이 평범하게 보여도, 파일 내용은 CP932로 저장되어 있을 수 있습니다. 반대로 파일 자체는 UTF-8이어도 콘솔 code page가 맞지 않으면 표시만 깨집니다.

chcp 65001 같은 조작도, 기본적으로는 콘솔 쪽 전제에 작용하는 이야기이지, 기존 파일의 바이트열을 바꾸는 이야기가 아닙니다.

게다가 소스 코드 파일이 UTF-8이어도, 실행 시 내보내는 로그 파일이 UTF-8이라고는 할 수 없습니다. 어느 계층의 문자 인코딩 이야기인지를 매번 나눠야 합니다.

참고로 일본어 Windows에서는 \가 엔 기호로 보이는 경우가 있고, 이것도 문자 인코딩 이야기와 섞이기 쉽습니다. 다만 많은 경우 이것은 표시 폰트나 글리프 문제이지, 경로 구분자나 이스케이프의 뜻이 바뀐 것은 아닙니다.

5.5 BOM과 줄바꿈도 별도 축으로 작용한다

UTF-8이라는 말만으로는 아직 절반만 정해진 것입니다.

실무에서는 다음도 작용합니다.

  • BOM이 있는가, 없는가
  • 줄바꿈은 CRLF인가 LF인가

예를 들어 같은 UTF-8이어도

  • BOM이 있으면 읽을 수 있는 Windows 도구가 있다
  • BOM이 있으면 첫 열에 여분 문자가 붙는 Unix 계열 처리가 있다
  • LF만 있으면 레거시 쪽에서 다루기 어려운 도구가 있다
  • CRLF이면 셸 스크립트나 diff가 지저분해지는 경우가 있다

처럼, 문자 인코딩이 맞아도 아직 사고가 납니다.

5.6 도구가 제멋대로 바꾼다

더 골치 아픈 것은, 사용 중인 도구가 암묵적으로 바꾼다는 점입니다.

  • 편집기가 자동 판정한다
  • 저장 시 BOM을 붙이거나 뺀다
  • Git이 CRLF / LF를 변환한다
  • 셸이나 명령이 기본 문자 인코딩으로 저장한다
  • CSV export가 예상 밖 code page를 쓴다
  • 다른 버전의 PowerShell이나 도구에서 기본값이 다르다

즉 작업자가 명시하지 않아도, 어디선가의 계층이 제멋대로 전제를 얹어 오는 것이 Windows 실무입니다.

이것이 「나는 아무것도 안 바꿨는데 깨졌다」의 정체입니다. 실제로는 사람이 아니라 도구의 기본값이 바꾸고 있는 경우가 적지 않습니다.

6. 흔한 사고 패턴

전형 사고를 표로 보면 다음과 같습니다.

장면 실제로 어긋난 것 전형 증상
UTF-8 no BOM 설정 파일을 레거시 Windows 도구가 ANSI / CP932로 본다 디코딩 전제 일본어만 깨진다
CP932 CSV를 UTF-8 전제 처리기로 넘긴다 디코딩 전제 , 디코딩 오류, 의미 없는 일본어
UTF-16LE 로그를 Unix 계열 텍스트 도구로 넘긴다 문자 인코딩 전제 NUL 바이트가 섞여 바이너리처럼 보인다
LF 소스 파일을 다른 환경에서 CRLF로 변환한다 줄바꿈 전제 거대한 줄 끝 차이, 스크립트 이상
잘못 읽은 내용을 그대로 저장한다 바이트열 자체가 다른 것이 된다 되돌릴 수 없는 데이터 손상
CSV를 내라고만 사양을 쓴다 인터페이스가 미정의 Excel에서는 읽히지만 다른 도구에서는 깨진다
UTF-8로 통일이라고만 정한다 BOM / 줄바꿈이 미정의 일부 도구만 실패한다

특히 위험한 것은 표시 깨짐을 본 뒤, 그대로 저장해서 사고를 확정하는 패턴입니다.

7. 실무에서 사고를 줄이는 규칙

여기서부터는 운영 규칙으로 무엇을 정하면 사고가 줄어드는지입니다.

7.1 신규 텍스트의 기본 방침을 정한다

신규 파일에서는 우선 UTF-8을 제1 후보로 삼는 것이 타당합니다. 다만 그것만으로는 부족합니다.

최소한 다음까지 정하는 편이 안전합니다.

  • UTF-8 with BOM인가 UTF-8 no BOM인가
  • 줄바꿈은 CRLF인가 LF인가
  • 누가 읽는 파일인가
  • 레거시 Windows 도구와의 호환이 필요한가
  • Linux / macOS / CI / 컨테이너도 읽는가

예를 들어 크로스 플랫폼 전제의 소스 코드나 설정 파일이라면 UTF-8 no BOM + LF가 제1 후보가 되기 쉽습니다. 반면 Windows의 오래된 도구나 기존 운영에 맞춰야 한다면 UTF-8 with BOM이나 CP932 + CRLF가 아직 필요한 경우도 있습니다.

중요한 것은 「무엇이 맞는가」라는 일반론보다, 누구와 주고받는가로 정하는 것입니다.

7.2 기존 레거시 파일은 제멋대로 바꾸지 않는다

기존 파일이 CP932라면, 일상적인 작은 수정의 김에 UTF-8로 바꾸지 않는 편이 안전합니다.

안전한 쪽 운영은 이렇습니다.

  • 기존 파일은 원래 문자 인코딩 / BOM / 줄바꿈을 유지한다
  • 문자 인코딩 변환은 별도의 이전 작업으로 나눈다
  • 변환 대상과 그 파일을 사용하는 쪽을 확인한 뒤 일괄 변환한다

문자 깨짐 사고는, 선의의 「김에 현대화」에서 나기 쉽습니다.

7.3 문자 인코딩과 줄바꿈을 인터페이스의 일부로 다룬다

CSV, TXT, 로그, 설정 파일, 간단한 프로토콜은 내용만이 아니라 텍스트 형식 자체가 인터페이스입니다.

사양에는 최소한 다음을 쓰는 편이 안전합니다.

  • 문자 인코딩
  • BOM 유무
  • 줄바꿈
  • 헤더 유무
  • 따옴표 / 구분자 사양
  • 어느 도구로 검증했는가

예를 들어 CSV라는 세 글자만으로는 부족합니다. UTF-8 with BOM, CRLF, 쉼표 구분, 헤더 있음까지 써야 비로소 대화가 덜 어긋납니다.

7.4 읽고 쓰는 경계에서는 명시한다

코드 쪽에서도 암묵적인 기본값에 기대지 않는 편이 안전합니다.

  • 파일을 읽고 쓸 때는 문자 인코딩을 명시한다
  • 프로세스 간 텍스트 전달에서도 문자 인코딩을 의식한다
  • export / import 처리에서는 줄바꿈도 사양으로 고정한다
  • 셸의 대충 리다이렉트를 운영 경로에 쓰지 않는다

특히 Windows에서는 「저장됐다」와 「올바른 바이트열로 저장됐다」는 같은 것이 아닙니다.

7.5 Git과 편집기 규칙도 공유한다

Git은 문자 인코딩을 자동으로 고쳐 주는 도구가 아닙니다. 반면 줄바꿈에 대해서는 변환이 들어가기도 합니다.

그래서 리포지터리 단위로 다음을 정하는 편이 안전합니다.

  • 소스 코드는 LF를 기본으로 할 것인가
  • Windows 전용 텍스트는 CRLF를 허용할 것인가
  • .gitattributes로 어떻게 고정할 것인가
  • 편집기 설정을 어떻게 공유할 것인가

문자 인코딩과 줄바꿈을 따로 생각하는 것이 중요합니다. Git이 줄바꿈을 맞춰 줘도, 문자 인코딩 사고는 그대로 남습니다.

7.6 「문자가 깨졌다」에서 멈추지 말고, 무엇이 어긋났는지 말한다

현장에서는 이 바꿔 말하기가 잘 먹힙니다.

  • 나쁜 말: 문자가 깨졌습니다
  • 좋은 말: UTF-8 no BOM 파일을 CP932 전제로 열고 있는 것으로 보입니다
  • 나쁜 말: 줄 끝이 이상합니다
  • 좋은 말: LF 파일이 CRLF로 변환되어 차이가 늘고 있습니다

무엇이 어긋났는지를 말할 수 있는 것만으로 조사 속도는 꽤 달라집니다.

7.7 확인하려면 - 도구별 볼 곳

여기까지의 규칙은 확인 수단과 세트로 둬야 비로소 돌아갑니다. 자주 쓰는 세 가지에 대해 「지금 문자 인코딩을 어디서 보는가」「어디서 바꾸는가」를 정리합니다.

도구 지금 문자 인코딩을 보는 곳 바꾸는 곳
VS Code 창 오른쪽 아래 상태 표시줄. UTF-8 같은 문자 인코딩과 CRLF / LF 줄바꿈 표시가 나란히 있습니다 상태 표시줄의 문자 인코딩 표시를 클릭하면 다시 열기와 저장 선택지가 나옵니다. 기본값은 설정 화면의 files.encoding
메모장 상태 표시줄에 문서의 인코딩이 표시됩니다 「파일」에서 「다른 이름으로 저장」을 열고, 대화 상자의 「인코딩」란에서 고릅니다
PowerShell 파일 맨 앞 바이트를 직접 보는 것이 확실합니다 (아래 명령) 쓰는 쪽 cmdlet의 -Encoding 매개변수. 기본값은 $PSDefaultParameterValues

VS Code

VS Code의 기본 문자 인코딩은 UTF-8 (BOM 없음) 입니다. 파일마다 바꿀 때는 상태 표시줄 표시를 클릭하고, 지금 연 파일을 다른 문자 인코딩으로 다시 열 것인지, 다른 문자 인코딩으로 다시 저장할 것인지를 고릅니다. 잘못 읽은 것뿐이라면 먼저 다시 연다는 점이 3.2의 판단과 대응합니다.

기본값을 바꿀 때는 설정의 files.encoding을 지정합니다. 값은 utf8 (BOM 없음), utf8bom (BOM 있음), utf16le, windows1252 등입니다. 언어별로 나눌 수도 있습니다.

{
  "files.encoding": "utf8",
  "[powershell]": {
    "files.encoding": "utf8bom"
  }
}

Windows PowerShell 5.1에서 돌리는 스크립트만 BOM 있음으로 한다는 2.4의 구분은, 이 언어별 설정으로 표현할 수 있습니다.

메모장

Windows 10 Build 18963 이후의 메모장은 상태 표시줄에 문서 인코딩을 표시하는 칸이 있고, 신규 파일의 기본이 UTF-8 (BOM 없음) 입니다. 저장 시에는 「파일」에서 「다른 이름으로 저장」을 열고, 대화 상자의 「인코딩」란에서 고릅니다. 이 란에서는 BOM 유무까지 고를 수 있으므로, 7.1에서 정한 방침을 여기에 적용합니다.

「메모장으로 열었더니 문자가 깨졌다」는 보고가 오면, 이 란과 상태 표시줄 표시를 먼저 확인하는 것이 빠릅니다.

PowerShell

PowerShell의 기본값은 버전마다 크게 다릅니다. 여기를 모른 채 「PowerShell로 출력했더니 도구가 못 읽는다」는 상담이 되는 경우가 많습니다.

  • PowerShell 6 이후 (PowerShell 7 계열): 모든 텍스트 출력의 기본이 utf8NoBOM, 즉 UTF-8 BOM 없음입니다.
  • Windows PowerShell 5.1: 기본값이 cmdlet마다 맞춰져 있지 않습니다.

5.1의 주요 항목을 표로 보면 다음과 같습니다.

명령 쓸 때의 기본
Out-File, 리다이렉트 연산자 > >> UTF-16LE
Set-Content, Add-Content (출력 대상이 비어 있거나 없을 때) Default (active code page. 일본어 환경에서는 CP932)
Export-Csv Ascii
Start-Transcript UTF-8 with BOM
New-Item -Type File -Value UTF-8 no BOM

읽는 쪽에도 기본값이 있습니다. BOM이 없을 때 Get-Content와 PowerShell 엔진이 스크립트 파일을 읽을 때는 Default (active code page)로 보고, Import-CsvSelect-String은 UTF-8로 봅니다. 같은 파일이라도 어느 cmdlet으로 읽느냐에 따라 전제가 바뀐다는 뜻입니다.

지금 이 파일에 BOM이 있는지는 맨 앞 바이트를 보면 알 수 있습니다.

# PowerShell 7: 맨 앞 3바이트를 16진으로 표시한다
Get-Content -Path .\sample.txt -AsByteStream -TotalCount 3 | Format-Hex

# Windows PowerShell 5.1에는 -AsByteStream이 없으므로 -Encoding Byte를 쓴다
Get-Content -Path .\sample.txt -Encoding Byte -TotalCount 3 | Format-Hex

맨 앞이 EF BB BF이면 UTF-8 with BOM, FF FE이면 UTF-16LE, 그 밖이면 BOM 없음입니다. 2.4의 표와 같은 보는 법입니다.

기본값을 명시적으로 고정하려면 $PSDefaultParameterValues를 씁니다.

# Encoding 매개변수가 있는 모든 cmdlet의 기본을 UTF-8로 한다
$PSDefaultParameterValues['*:Encoding'] = 'utf8'

여기에 버전 간 함정이 있습니다. utf8이라는 같은 문자열이라도 Windows PowerShell 5.1에서는 UTF-8 with BOM, PowerShell 6 이후에서는 UTF-8 no BOM을 뜻합니다. BOM 유무까지 고정하려면 PowerShell 6 이후에서 쓸 수 있는 utf8BOM / utf8NoBOM을 명시하는 편이 안전합니다.

참고로 $OutputEncoding외부 프로그램과의 전달에 쓰는 문자 인코딩이며, 리다이렉트나 cmdlet이 파일에 저장할 때의 문자 인코딩에는 영향을 주지 않습니다. 여기가 혼동하기 쉬운 지점입니다.

8. 문자 깨짐·줄 끝 차이는 이 5가지 질문으로 조사한다

조사에서 헤매면 다음 다섯 질문으로 돌아가는 것이 가장 빠릅니다.

  1. 지금 이 파일의 바이트열은 무엇인가
    • UTF-8인가
    • UTF-8 with BOM인가
    • CP932인가
    • UTF-16LE인가
  2. 처음에 누가, 어느 전제로 썼는가
    • 편집기
    • 레거시 앱
    • Excel export
    • 셸 / 스크립트
    • 배치 / 미들웨어
  3. 지금 누가, 어느 전제로 읽고 있는가
    • 편집기의 자동 판정
    • 콘솔의 code page
    • 라이브러리의 기본 문자 인코딩
    • import 측 사양
  4. BOM과 줄바꿈은 무엇인가
    • BOM 있음 / 없음
    • CRLF / LF
  5. 잘못 읽은 내용이 이미 저장되었는가
    • 아직 표시만인가
    • 이미 다시 저장되어 바이트열이 사라졌는가

이 다섯 질문이 채워지면 대개 원인이 보입니다.

9. 정리

Windows의 문자 인코딩과 줄바꿈이 복잡해 보이는 것은 일본어 자체가 어려워서가 아닙니다. 바이트열, 문자 인코딩, BOM, 줄바꿈, 도구의 기본값이 따로 존재하고, 그 위에서 Windows에는 옛것과 새것의 텍스트 문화가 같이 살기 때문입니다.

특히 기억해 두고 싶은 것은 다음 여섯 가지입니다.

  • 문자 깨짐은 같은 바이트열을 다른 문자 인코딩으로 읽은 결과
  • 줄바꿈 문제는 문자 인코딩과는 다른 축의 이야기
  • Shift_JIS, CP932, ANSI, Unicode라는 말을 그대로 너무 믿지 않는다
  • UTF-8로 했다만으로는 부족하고, BOM과 줄바꿈도 필요하다
  • 표시 깨짐과, 이미 다시 저장한 데이터 손상은 나눠 생각한다
  • 사양에서는 텍스트가 아니라 UTF-8 no BOM, LF처럼 쓴다

요컨대 Windows 텍스트를 다룰 때는 「문자열 이야기」가 아니라, 바이트열의 약속을 어떻게 맞출 것인가의 이야기라고 보는 것이 실무적입니다.

10. 관련 글

11. 참고 자료

  1. Microsoft Learn, Code Page Identifiers - Win32 apps https://learn.microsoft.com/en-us/windows/win32/intl/code-page-identifiers
  2. Microsoft Learn, about_Character_Encoding - PowerShell https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_character_encoding?view=powershell-7.6
  3. Microsoft Learn, Understanding file encoding in VS Code and PowerShell https://learn.microsoft.com/en-us/powershell/scripting/dev-cross-plat/vscode/understanding-file-encoding?view=powershell-7.6
  4. W3C Internationalization, Character encodings: Essential concepts https://www.w3.org/International/articles/definitions-characters/
  5. Git documentation, gitattributes https://git-scm.com/docs/gitattributes
  6. Git documentation, git-config https://git-scm.com/docs/git-config
  7. Microsoft Learn, The Unicode standard - Globalization (BOM의 바이트열과, UTF-8의 BOM이 시그니처로 쓰인다는 점) https://learn.microsoft.com/en-us/globalization/encoding/unicode-standard
  8. Microsoft Learn, What was new in 20H1 Windows 10 Insider Preview Builds (Build 18963에서 메모장의 UTF-8 기본화와, 상태 표시줄에 인코딩 표시 추가) https://learn.microsoft.com/en-us/previous-versions/windows-insider/archive/new-in-20h1

같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.

이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.

이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.

Windows 앱 개발

Windows용 업무 도구에서는 CP932와 UTF-8이 혼재하는 현장이 많고, 문자 인코딩과 줄바꿈 처리를 설계에 넣어 두는 것이 유지보수에 직결됩니다.

자주 묻는 질문

이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.

문자 깨짐은 왜 일어나나요?
같은 바이트열을, 저장할 때와 다른 문자 인코딩으로 읽었기 때문입니다. 예를 들어 「あ」를 UTF-8로 저장하면 E3 81 82라는 바이트열이 되지만, 이를 CP932 전제로 읽으면 「縺」과 같은 다른 문자열로 보입니다. 이때 깨진 것은 일본어가 아니라 디코딩 전제입니다. 다만 잘못 읽은 내용을 그대로 다시 저장하면 원래 바이트열이 사라지고, 표시만 깨진 상태가 아니라 데이터 손상이 되므로, 저장 전에 올바른 문자 인코딩으로 다시 여는 것이 중요합니다.
CRLF와 LF의 차이는 무엇인가요?
줄을 나누는 데 쓰는 바이트열의 차이입니다. CRLF는 0D 0A 2바이트로 Windows 텍스트 파일에서 전통적으로 쓰이고, LF는 0A 1바이트로 Linux / macOS와 많은 개발 도구에서 일반적입니다. 중요한 점은 줄바꿈이 문자 인코딩과는 별개 문제라는 것입니다. 같은 UTF-8 파일이라도 줄바꿈은 CRLF일 수도 LF일 수도 있으므로, 「UTF-8로 바꿨는데도 아직 다르다」는 때는 문자 인코딩이 아니라 줄바꿈만 어긋난 경우가 있습니다.
Shift_JIS와 CP932는 같은 것인가요?
대화로는 통하지만, 실무에서는 구별하는 편이 안전합니다. 일본어 Windows의 레거시 텍스트를 정확히 의식하려면 CP932 또는 Windows 계열의 일본어 code page로 생각합니다. 마찬가지로 Windows에서 「ANSI」는 대부분 그 머신의 active code page를 가리키고, 편집기 메뉴의 「Unicode」는 UTF-16LE를 의미하는 경우가 있습니다. 사양이나 조사 메모에서는 「UTF-8 no BOM, LF」처럼 구체적으로 쓰면 대화가 어긋나기 어렵습니다.
텍스트 파일 사양은 무엇을 정해 두면 되나요?
「UTF-8로 했다」만으로는 부족하고, BOM 유무와 줄바꿈까지 정해야 비로소 운영 규칙이 됩니다. CSV나 로그처럼 주고받는 파일에서는 문자 인코딩, BOM 유무, 줄바꿈, 헤더 유무, 구분자 사양까지 쓰는 것이 안전합니다. 크로스 플랫폼 전제의 소스 코드나 설정이라면 UTF-8 no BOM + LF가 제1 후보가 되기 쉬운 반면, Windows의 오래된 도구에 맞춰야 한다면 UTF-8 with BOM이나 CP932 + CRLF가 아직 필요한 경우도 있습니다.

저자 프로필

기사 저자의 프로필 페이지입니다.

Go Komura

합동회사 코무라소프트 대표

Windows 소프트웨어 개발, 기술 상담, 장애 조사를 중심으로 재현이 어려운 장애 조사와 기존 자산이 남아 있는 프로젝트에 강점이 있습니다.

블로그 목록으로 돌아가기