빌드노트
Get-Content 결과를 문자열 인자에 넘겼더니 줄바꿈이 사라진다
기본 Get-Content로 읽은 줄 배열을 일반 함수의 문자열 인자에 넘기면 공백으로 이어질 수 있다. PowerShell 7.6.6에서 타입·요소 수·Length와 인자 전달 결과를 비교하고, -Raw가 LF·CRLF와 마지막 개행을 보존하는지 확인했다.
파일은 세 줄인데 PowerShell 함수가 받은 값은 red green blue처럼 한 줄이 될 수 있다. 기본 Get-Content가 줄마다 내보낸 문자열들을 모아 놓고, 그 배열을 일반 함수의 [string] 매개변수에 넘겼을 때 생기는 차이다. 파일 전체를 줄바꿈이 있는 문자열 하나로 쓰려면 읽는 단계에서 -Raw를 지정한다. 문자열로 강제 변환하는 것만으로 원래 줄바꿈이 돌아오지는 않는다.
자기 스크립트에서는 파일을 읽는 줄과 그 값을 받는 매개변수의 타입을 먼저 확인한다. $path가 실제 UTF-8 파일 경로라면 Get-Content -LiteralPath $path -Encoding utf8 -Raw로 전체 텍스트를 읽을 수 있다. 줄마다 검사하거나 처리하려는 코드에는 기본 읽기가 맞을 수 있다.
같은 파일인데 Length의 뜻이 다르다
아래는 Windows의 PowerShell 7.6.6에서 별도로 재현한 예다. PowerShell 7이 설치된 환경에서 pwsh -NoProfile로 새 셸을 열고, 비어 있는 연습 폴더에서 실행한다. 추가 패키지는 필요 없다. message.txt에 세 줄과 마지막 LF 개행을 명시해서 쓰므로 편집기의 개행 설정에 의존하지 않는다. 읽을 때도 UTF-8을 지정한다.
다음 코드는 같은 파일을 두 방식으로 읽고 타입, 결과 객체 수, Length를 비교한다.
$path = Join-Path $PWD 'message.txt'
$expected = "red`ngreen`nblue`n"
[IO.File]::WriteAllText($path, $expected, [Text.UTF8Encoding]::new($false))
$lines = Get-Content -LiteralPath $path -Encoding utf8
$raw = Get-Content -LiteralPath $path -Encoding utf8 -Raw
"lines: type=$($lines.GetType().FullName), count=$(@($lines).Count), length=$($lines.Length)"
"raw: type=$($raw.GetType().FullName), count=$(@($raw).Count), length=$($raw.Length)"
출력은 다음과 같았다.
lines: type=System.Object[], count=3, length=3
raw: type=System.String, count=1, length=15
$lines에는 red, green, blue라는 문자열 세 개가 들어 있었다. 이 배열의 Length는 요소 수 3이다. $raw는 줄바꿈까지 담은 문자열 하나이므로 Length가 텍스트 길이 15였다. 문자열의 이 값은 UTF-16 코드 단위 수이며 파일의 바이트 수를 뜻하지 않는다. .NET String.Length 문서 (새 탭에서 열림)는 문자열의 Char 개수를 반환한다고 설명한다. @(...).Count는 여기서 결과 객체 수를 세기 위한 것으로, $raw의 실제 줄 수를 세는 식이 아니다.
Get-Content 공식 문서의 Raw 설명 (새 탭에서 열림)도 기본 읽기는 개행으로 나누고, -Raw는 개행을 보존한 문자열 하나를 반환한다고 구분한다. 콘솔에 둘 다 세 줄로 보이는지만 봐서는 이 차이를 알기 어렵다.
문자열로 바뀌는 순간 공백이 들어간다
이번에는 앞에서 읽은 두 값을 같은 일반 함수에 넘긴다. 다음 함수는 받은 문자열에서 CR과 LF만 눈에 보이는 표기로 바꾼다. 실제 입력에 <CR>이나 <LF>라는 글자가 들어 있는 것은 아니다.
function Show-Text {
param([string]$Text)
$Text.Replace("`r", '<CR>').Replace("`n", '<LF>')
}
Show-Text -Text $lines
Show-Text -Text $raw
[string]$lines
순서대로 다음 결과를 얻었다.
red green blue
red<LF>green<LF>blue<LF>
red green blue
첫 호출은 배열을 [string]으로 변환하면서 세 요소 사이에 공백을 넣었다. 두 번째 호출은 이미 문자열인 $raw를 받아 마지막 개행까지 유지했다. 세 번째 줄처럼 직접 형변환해도 배열은 원문이 되지 않았다. 같은 환경에서 "$lines"로 문자열 안에 넣었을 때도 red green blue였다.
PowerShell 타입 변환 문서 (새 탭에서 열림)는 배열을 문자열로 변환할 때 기본적으로 공백으로 잇고, 구분자는 $OFS로 바꿀 수 있다고 설명한다. 이번 비교는 기본 구분자인 공백을 사용했다. 따라서 출력의 공백은 파일에 있던 줄바꿈이 보존된 결과가 아니다.
다만 위 함수에는 [CmdletBinding()]이 없다. 이 속성을 넣은 고급 함수에 $lines를 넘기는 별도 비교에서는 ParameterArgumentTransformationError로 바인딩이 거부됐다. $raw는 같은 고급 함수에서도 원문대로 전달됐다. 이 차이도 공식 문서의 매개변수 바인딩 설명 (새 탭에서 열림)에 나온다. 모든 문자열 매개변수가 배열을 조용히 공백으로 잇는다고 가정하면 안 된다. 오류가 나는 경우에도 호출부가 줄 배열을 넘기는지 확인한다.
줄을 다시 붙여도 마지막 개행은 복구되지 않는다
LF로 잇는 -join을 쓰면 화면상 줄바꿈은 만들 수 있다. 하지만 앞의 파일에는 마지막 blue 뒤에도 LF가 있다. 다음 두 비교는 읽은 문자열과 다시 조립한 문자열을 각각 처음 쓴 텍스트와 비교한다.
[string]::Equals($raw, $expected, [StringComparison]::Ordinal)
[string]::Equals(($lines -join "`n"), $expected, [StringComparison]::Ordinal)
출력은 True, False였다. -join은 요소 사이만 이어서 마지막 LF를 추가하지 않았다. 새 형식으로 줄을 조립하는 작업에는 쓸 수 있지만, 원래 파일의 개행을 보존하는 작업과는 다르다.
같은 세 줄을 LF와 CRLF로 저장하고, 각각 마지막 개행이 있는 경우와 없는 경우도 비교했다. 네 조건 모두 기본 읽기는 같은 세 요소를 반환했다. 반면 -Raw는 네 조건 모두 처음 쓴 텍스트와 정확히 같았고, 일반 함수의 문자열 인자로 전달한 뒤에도 같았다. CRLF 파일을 LF로 다시 이은 두 경우 역시 원문과 달랐다. 줄 배열만 남았다면 원래 개행 종류와 마지막 개행 유무를 알아낼 수 없다는 뜻이다.
원문이 필요한 코드에서는 배열을 받은 뒤 구분자를 맞추기보다 파일을 읽는 줄에 -Raw를 적용한다. 이번에 확인한 범위는 PowerShell 7.6.6의 BOM 없는 UTF-8 세 줄 파일과 PowerShell 함수 호출이다. Windows PowerShell 5.1, 외부 실행 파일의 인자·표준입력 처리는 검증하지 않았다. 읽은 뒤 다시 파일에 쓰거나 전송하는 단계가 개행을 바꾸는지도 그 단계에서 따로 확인해야 한다.