본문 바로가기
빌드노트 검색

빌드노트

PowerShell 함수에 return $false가 있는데 if가 참이 된다

PowerShell 함수에서 진단 문자열을 그대로 출력하면 return $false 앞의 문자열도 결과에 포함된다. 원소·타입·Boolean 변환을 확인하고 Write-Verbose로 분리하는 수정, 4>&1로 다시 합칠 때의 경계를 실제 비교한다.

PowerShell 함수에 return $false를 넣었는데 if ($result)의 참 분기가 실행된다면, $result가 Boolean 하나인지 먼저 확인한다. 함수 안에서 확인용 문자열을 따옴표로만 적으면 그 문자열도 함수의 결과에 포함된다. return은 앞서 나온 출력을 지우지 않는다. 진단은 Write-Verbose로 옮기고 데이터용 출력에는 의도한 Boolean만 남긴다. PowerShell의 return 설명 (새 탭에서 열림)

자기 함수에서는 return 위의 진단 문자열과 Write-Output 호출부터 살핀다. 결과를 따로 받지 않은 명령의 출력도 확인 대상이다. 호출부에서 [bool]로 강제 변환하는 것만으로는 섞여 들어간 출력이 없어지지 않는다.

False 하나를 받은 것이 아니었다

다음은 Windows의 PowerShell 7.6.6에서 별도로 실행한 합성 예제다. PowerShell 7이 설치된 터미널에서 pwsh -NoProfile로 새 셸을 열면 된다. 추가 패키지나 입력 파일은 필요 없다. 이 코드는 함수 결과의 타입, 원소 수, 각 원소와 조건문의 선택을 확인한다.

function Test-Ready {
    'checking readiness'
    return $false
    'after return'
}

$result = Test-Ready
$result.GetType().FullName
@($result).Count
$result | ForEach-Object { '{0}: {1}' -f $_.GetType().Name, $_ }
[bool]$result
if ($result) { 'ready' } else { 'not ready' }

출력은 다음과 같았다.

System.Object[]
2
String: checking readiness
Boolean: False
True
ready

$result는 문자열과 Boolean을 담은 배열이었다. 마지막 원소가 실제 Boolean False인 것과 배열 전체가 거짓으로 평가되는 것은 다른 문제다. PowerShell에서 원소가 둘 이상인 배열은 Boolean으로 변환하면 참이다. 공식 문서도 빈 컬렉션, 원소 하나인 컬렉션, 여러 원소인 컬렉션의 평가를 구분한다. 컬렉션의 Boolean 변환 규칙 (새 탭에서 열림)

이 실험에서는 [bool]@($false, $false)도 True였다. 진단 문자열의 내용이 참을 뜻해서 생긴 결과가 아니다. 반면 진단 줄을 지우고 return $false만 남긴 대조에서는 타입이 System.Boolean, 원소 수가 1, Boolean 변환 결과가 False였다.

after return이 출력되지 않은 것도 함께 본다. return은 함수 실행을 그 자리에서 끝냈다. 다만 그 전에 내보낸 checking readiness까지 취소하지는 않았다.

진단을 보이게 해도 결과는 하나로 남긴다

PowerShell은 결과 데이터와 진단 메시지를 서로 다른 스트림으로 보낼 수 있다. 일반 결과를 전달하는 Success 스트림은 1번이고, Write-Output도 여기에 쓴다. Write-Verbose는 진단용인 4번 Verbose 스트림에 쓴다. 따라서 진단 문자열을 Write-Output으로 감싸는 수정은 이 문제를 해결하지 못한다. 출력 스트림과 용도 (새 탭에서 열림)

앞의 함수를 다음 정의로 교체한다. 진단 한 줄을 Write-Verbose로 바꾸고, 함수 호출에 -Verbose를 줄 수 있도록 [CmdletBinding()]과 param()을 넣었다. 이 속성은 공통 매개변수를 추가한다. CmdletBinding의 공통 매개변수 (새 탭에서 열림)

다음 코드는 진단을 켠 상태에서도 변수에 Boolean 하나만 들어오는지 확인한다.

function Test-Ready {
    [CmdletBinding()]
    param()
    Write-Verbose 'checking readiness'
    return $false
}

$result = Test-Ready -Verbose
$result.GetType().FullName
@($result).Count
[bool]$result
if ($result) { 'ready' } else { 'not ready' }

같은 환경에서 다음 출력을 얻었다.

자세한 정보: checking readiness
System.Boolean
1
False
not ready

진단 접두사는 이 실험의 한국어 환경에서 표시된 값이다. 확인할 부분은 그 아래의 System.Boolean, 1, False와 not ready다. 진단이 화면에 보였지만 $result에는 들어가지 않았다. -Verbose 없이 호출한 대조에서도 결과의 타입·원소 수·Boolean 값은 같았고 진단은 보이지 않았다. Write-Verbose는 기본적으로 표시하지 않으며, -Verbose로 표시할 수 있다. 이 예제는 환경 전체의 $VerbosePreference를 바꾸지 않는다. Write-Verbose의 표시 조건 (새 탭에서 열림)

호출부에서 다시 합치면 같은 문제가 생긴다

함수를 고친 뒤에도 결과가 배열이라면 호출부의 리다이렉션을 본다. 4>&1은 Verbose 스트림을 Success 스트림으로 합친다. 앞에서 정의한 함수를 그대로 두고 다음 코드를 실행하면, 메시지가 다시 결과에 포함되는지와 메시지만 버렸을 때를 비교할 수 있다. 스트림 리다이렉션 연산자 (새 탭에서 열림)

$merged = Test-Ready -Verbose 4>&1
$merged | ForEach-Object { '{0}: {1}' -f $_.GetType().Name, $_ }
[bool]$merged

$clean = Test-Ready -Verbose 4>$null
$clean.GetType().FullName
[bool]$clean

출력은 다음과 같았다.

VerboseRecord: checking readiness
Boolean: False
True
System.Boolean
False

병합한 $merged에는 VerboseRecord와 Boolean의 두 원소가 들어 있었다. 같은 호출에 *>&1을 사용한 대조도 원소 수가 2이고 참이었다. 모든 스트림을 모으는 호출은 로그 수집에 쓸 수 있지만, 그 묶음을 함수의 Boolean 결과로 판정하면 안 된다.

4>$null은 4번 진단만 버려 $clean에 Boolean False를 남겼다. 진단을 보고 싶다면 앞의 예처럼 병합 없이 -Verbose만 사용한다. 2>&1은 오류 스트림을 합치는 연산으로, 4번 진단을 합치는 연산과 다르다. 처음의 잘못된 함수에 4>$null이나 2>$null을 붙인 대조에서는 여전히 두 원소와 True가 나왔다. 따옴표로만 내보낸 문자열은 이미 1번에 있었기 때문이다.

기존 코드에 적용한 뒤에는 함수 호출 직후 결과의 타입과 원소를 다시 확인한다. 여전히 여러 원소가 잡힌다면 다른 명령이 Success 스트림에 내보낸 객체와 호출부의 병합을 차례로 추적한다. 마지막 원소만 꺼내 조건문에 넣는 우회는 함수가 내보내는 불필요한 데이터를 그대로 남긴다.

직접 실행한 범위는 Windows 10.0.26200의 PowerShell 7.6.6에서 새 -NoProfile 자식으로 실행한 합성 함수다. Windows PowerShell 5.1, 다른 운영체제, 클래스 메서드와 외부 프로세스의 출력 처리는 검증하지 않았다. 실제 업무 스크립트의 장애를 관찰한 기록은 아니다.

← 목록으로 돌아가기