PowerShell UTF-8 BOM CRLF, 파일 쓰는 명령마다 결과가 달랐다
지난 글에서 git 시험을 하던 날, 한글이 든 PowerShell 스크립트를 돌렸더니 문법 오류가 났다. 근데 오류 난 줄엔 아무 문제가 없었다. 알고 보니 인코딩이었다. 내친김에 파일 쓰는 명령마다 결과가 어떻게 다른지 다 재 봤다.
이번 시험에선 LF 를 넣은 문자열을 [IO.File]::WriteAllText 에 UTF8Encoding($false) 와 함께 넘겼더니 BOM 없는 UTF-8 로 써졌다. 끝에 CRLF 도 붙지 않았다. 윈도우 PowerShell 5.1 기본값으로는 Out-File 이랑 > 는 UTF-16, Set-Content 는 시스템 코드페이지, -Encoding UTF8 은 BOM 을 붙였고, 줄 끝도 CRLF 였다.
PowerShell 5.1 명령별로 직접 재 봤다
말로만 들으면 헷갈려서 직접 쟀다. 프로필 영향을 빼려고 powershell.exe -NoProfile 로 새로 띄웠다. "가나", 줄바꿈 LF, "다" 이렇게 같은 문자열을 명령마다 파일로 쓰고, 파일 앞 4바이트랑 CR(0D) 개수를 셌다.

| 명령 | 앞 바이트 | 뜻 | CR |
|---|---|---|---|
| Set-Content | B0 A1 B3 AA | CP949(한국어 코드페이지) | 1 |
| Set-Content -Encoding UTF8 | EF BB BF EA | UTF-8, BOM 있음 | 1 |
| Out-File | FF FE 00 AC | UTF-16LE, BOM 있음 | 1 |
| Out-File -Encoding utf8 | EF BB BF EA | UTF-8, BOM 있음 | 1 |
| > 리다이렉트 | FF FE 00 AC | UTF-16LE, BOM 있음 | 1 |
| [IO.File]::WriteAllText | EA B0 80 EB | UTF-8, BOM 없음 | 0 |
같은 문자열인데 결과가 네 가지. 솔직히 좀 어이없었다.
CR 이 1인 것도 재밌다. 문자열 안의 LF 는 그대로 두고, 명령이 끝에 붙이는 줄바꿈만 CRLF 로 썼다. 그러니까 한 파일 안에 LF 랑 CRLF 가 섞인다. WriteAllText 는 끝에 아무것도 안 붙여서 0 이다.
PowerShell 인코딩 문서를 열어 보니 측정이랑 그대로 맞았다. 5.1 에서 Out-File 이랑 > 는 UTF-16LE, Set-Content 는 시스템 ANSI 코드페이지를 쓰고, UTF8 은 BOM 이 붙는다고 적혀 있다. 문서에도 기본 인코딩이 일관되지 않다고 써 놨다.
프로필에 따라 결과가 또 달랐다
같은 PC 에서 평소 쓰는 셸로도 인코딩을 재 봤다. 이번엔 "가나"만 써서 파일 앞 바이트를 확인했다. > 랑 Out-File 은 UTF-8 BOM 으로 나왔다. 알고 보니 이 셸 프로필에 $PSDefaultParameterValues 로 Out-File 기본 인코딩이 잡혀 있었다.
$PSDefaultParameterValues.Keys
Out-File:Encoding
근데 Set-Content 는 여전히 CP949 였다. Out-File 만 바꿔 놨으니까. 문서에도 > 는 Out-File 을 부르는 거라 이 설정을 따른다고 나와 있다. 이런 기본 인코딩 설정이 있으면 다른 PC 나 -NoProfile 로 돌렸을 때 파일 인코딩이 달라질 수 있다.
한글 든 .ps1 이 깨진 이유
측정 스크립트를 처음엔 BOM 없는 UTF-8 로 저장했다. 한글 문자열이 들어 있었는데 돌리자마자 이랬다.
Unexpected token 'PSVersion' in expression or statement.
The string is missing the terminator: ".
오류 난 줄은 멀쩡했다. 5.1 은 BOM 없는 스크립트를 시스템 ANSI 코드페이지로 읽는다고 문서에 나와 있고, BOM 없는 UTF-8 스크립트에 ASCII 가 아닌 글자가 있으면 깨질 수 있다고도 적혀 있다. 이번 오류도 한글이 든 UTF-8 스크립트를 그렇게 읽으면서 난 걸로 보인다. 한글을 [char]0xAC00 같은 코드로 바꾼 뒤엔 측정이 돌아갔다. 스크립트를 UTF-8 BOM 으로 저장해도 된다고 문서엔 나오는데 그건 안 해 봤다.
BOM 없는 UTF-8 에 LF 로 쓰는 법
결국 내가 쓰는 건 이 한 줄이다.
[IO.File]::WriteAllText($path, $text, (New-Object Text.UTF8Encoding $false))
이 코드는 줄바꿈을 바꾸지 않는다. LF 로 쓰려면 $text 안의 줄바꿈부터 LF 여야 하고, CRLF 가 들어 있으면 그대로 남는다. 나는 $path 를 늘 전체 경로로 준다.
파일이 어떻게 써졌는지 볼 때는 앞 바이트를 찍어 본다.
[IO.File]::ReadAllBytes($path)[0..3] | ForEach-Object { '{0:X2}' -f $_ }
EF BB BF 로 시작하면 BOM, FF FE 면 UTF-16 이다. PowerShell 7 은 기본이 BOM 없는 UTF-8 이라고 문서에 나오는데 나는 5.1 만 써서 확인은 못 했다.
다음엔 이 블로그를 Node 스크립트 하나로 만든 얘기를 하려고 한다.