programing

$LastExitCode=0입니다만 $?= PowerShell에서 거짓.stdout으로 stderr을 리디렉션하면 Native CommandError가 발생함

cafebook 2023. 8. 11. 22:36
반응형

$LastExitCode=0입니다만 $?= PowerShell에서 거짓.stdout으로 stderr을 리디렉션하면 Native CommandError가 발생함

PowerShell이 아래 두 번째 예제에서 놀라운 동작을 보이는 이유는 무엇입니까?

첫째, 제정신인 행동의 예:

PS C:\> & cmd /c "echo Hello from standard error 1>&2"; echo "`$LastExitCode=$LastExitCode and `$?=$?"
Hello from standard error
$LastExitCode=0 and $?=True

놀랄 일이 아닙니다.합니다.cmdecho ) 를 검사합니다.$?그리고.$LastExitCode이 값은 예상대로 각각 참 및 0과 같습니다.

그러나 PowerShell에 첫 번째 명령을 통해 표준 오류를 표준 출력으로 리디렉션하도록 요청하면 NativeCommandError:

PS C:\> & cmd /c "echo Hello from standard error 1>&2" 2>&1; echo "`$LastExitCode=$LastExitCode and `$?=$?"
cmd.exe : Hello from standard error
At line:1 char:4
+ cmd <<<<  /c "echo Hello from standard error 1>&2" 2>&1; echo "`$LastExitCode=$LastExitCode and `$?=$?"
    + CategoryInfo          : NotSpecified: (Hello from standard error :String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError

$LastExitCode=0 and $?=False

첫 번째 질문입니다. Native Command Error가 발생한 이유는 무엇입니까?

왜 두번로, 는유이입니다.$?cmd성공적으로 실행되고,$LastExitCode0입니까? 자동 변수에 대한 PowerShell의 문서에는 다음과 같은 기능이 명시적으로 정의되어 있지 않습니다.$?나는 항상 그것이 진실이라고 생각했습니다 만약 그리고 오직 그렇다면.$LastExitCode0이지만, 제 예는 그것과 모순됩니다.


이것이 제가 실제 세계에서 어떻게 이런 행동을 접하게 된 계기입니다(간체화).정말 FUBAR입니다.PowerShell 스크립트를 다른 스크립트에서 호출했습니다.내부 스크립트:

cmd /c "echo Hello from standard error 1>&2"
if (! $?)
{
    echo "Job failed. Sending email.."
    exit 1
}
# Do something else

이를 단순하게 실행하는 것은.\job.ps1정상적으로 작동하며 이메일이 전송되지 않습니다.다른 에서 PowerShell의 PowerShell ..\job.ps1 2>&1 > log.txt이 경우 이메일이 전송됩니다!오류 스트림을 사용하여 스크립트 외부에서 수행하는 작업은 스크립트의 내부 동작에 영향을 미칩니다.현상을 관찰하면 결과가 바뀝니다.이것은 스크립팅이라기 보다는 양자 물리학처럼 느껴집니다!

도:].\job.ps1 2>&1도 있고 않을 수도 .

(PowerShell v2를 사용하고 있습니다.)

더'$?는 변는에문있어다습에 나와 .about_Automatic_Variables:

$?마지막 작업의 실행 상태를 포함합니다.

는 마지막 명령어인 PowerShell과 합니다. PowerShell은 PowerShell의 외부 명령어입니다.$LastExitCode.

예에서, 를들어예는,$LastExitCode0이었기 때문에 0입니다.cmd일부 텍스트를 반향시키는 데 성공했습니다.그런데 그.2>&1를 메지의원인으로 .stderr출력 스트림의 오류 레코드로 변환됩니다. 이 레코드는 PowerShell에 마지막 작업 중에 오류가 발생하여 다음과 같은 문제가 발생했음을 알려줍니다.$?되려고False.

이를 좀 더 설명하기 위해 다음 사항을 고려합니다.

> java -jar foo; $?; $LastExitCodejarfile foo에 액세스할 수 없습니다.거짓의1

$LastExitCode이는 java.exe의 종료 코드였기 때문에 1입니다.$?셸이 마지막으로 한 일이 실패했기 때문에 거짓입니다.

하지만 내가 하는 일이 그것들을 바꾸는 것뿐이라면:

> java -jar foo; $LastExitCode; $?
jarfile foo에 액세스할 수 없습니다.1
진실의

...그리고나서$?셸이 마지막으로 한 일은 인쇄였기 때문에 사실입니다.$LastExitCode호스트에게 성공적으로 전송이 성공적이었습니다.

마지막으로:

> &{java -jar foo}; $?; $LastExitCodejarfile foo에 액세스할 수 없습니다.진실의1

반대되는 ...약간 반대되는 것처럼 보이지만,$?스크립트 블록 내부에서 실행되는 명령이 성공하지 못했더라도 스크립트 블록이 성공적으로 실행되었기 때문에 true입니다.


2>&1리디렉션...출력 , 는 출력 스트림에 입니다.NativeCommandError셸이 전체 오류 기록을 폐기하고 있습니다.

이것은 당신이 파이프만 하고 싶을 때 특히 짜증날 수 있습니다.stderr 그리고. stdout로그 파일이나 다른 것으로 결합할 수 있도록 함께.PowerShell이 자신의 로그 파일에 삽입되기를 원하는 사람?내가 하면,ant build 2>&1 >build.log 그다에발모오류든는생하로 가는 .stderr로그 파일에 오류 메시지를 확인하는 대신 PowerShell의 까다로운 $0.02를 추가했습니다.

그러나 출력 스트림은 텍스트 스트림이 아닙니다!리디렉션은 개체 파이프라인의 또 다른 구문일 뿐입니다.오류 레코드는 개체이므로 리디렉션하기 전에 해당 스트림의 개체를 문자열로 변환하기만 하면 됩니다.

시작:

> cmd /c "echo Hello from standard error 1>&2" 2>&1cmd.exe : 표준 오류에서 Hello라인:1 문자:4cmd &2" 2>&1카테고리정보: 지정되지 않음: (표준 오류:String에서 Hello) [], 원격 예외Fully Qualified ErrorId: 네이티브 명령 오류

받는 사람:

> cmd /c "echo Hello from standard error 1>&2" 2>&1 | %{ "$_" }표준 오류에서 Hello

...파일로 리디렉션하는 경우:

> cmd /c "echo Hello from standard error 1>&2" 2>&1 | %{"$_"} | 티아웃.txt표준 오류에서 Hello

...아니면 그냥:

> cmd /c "echo Hello from standard error 1>&2" 2>&1 | %{"$_"} > out.txt

이 버그는 오류 처리를 위한 PowerShell의 규범적인 설계의 예상치 못한 결과이므로 수정되지 않을 가능성이 높습니다.스크립트가 다른 PowerShell 스크립트에서만 재생되는 경우 안전합니다.그러나 스크립트가 광범위한 응용 프로그램과 상호 작용하면 이 버그가 발생할 수 있습니다.

PS> nslookup microsoft.com 2>&1 ; echo $?

False

알았어요! 그래도, 고통스럽게 긁고 나면, 교훈을 절대 잊지 못할 거예요.

사용하다($LastExitCode -eq 0)$?

업데이트: v7.2에서 문제가 해결되었습니다. 이 답변을 참조하십시오.


v7.1 기준 문제 요약:

외부 프로그램 호출에 적용되는 리디렉션과 관련하여 PowerShell 엔진에 여전히 버그가 있습니다.

근본 원인은 를 사용하면 PowerShell의 오류 스트림(about_Redirection 참조)을 통해 stderr(표준 오류) 출력라우팅되므로 다음과 같은 바람직하지 않은 결과가 발생합니다.

  • 한다면$ErrorActionPreference = 'Stop'실로적됨용, 사용을 사용하여 합니다.2>예기치 않게 스크립트 종료 오류를 트리거합니다. 즉, 스크립트를 중단합니다(폼에서도 마찬가지).2>$null여기서 stderr 라인을 무시하려는 의도가 분명합니다.)GitHub 이슈 #4002를 참조하십시오.

    • 방법: ( 설정하기: (임시) 설정하기$ErrorActionPreference = 'Continue'
  • 때부터2>현재오스터치을합다니를 터치합니다.$? 자동성상변항상다음같설이다정니됩과수로 됩니다.$False하나 이상의 stderr 라인이 방출되어 더 이상 명령의 실제 성공 상태를 반영하지 않는 경우.GitHub 문제를 참조하십시오.

    • 답변에서 권장하는 해결 방법: 항상 사용만 가능$LASTEXITCODE -eq 0외부 프로그램 호출 후 성공 여부를 테스트합니다.
  • 와 함께2>▁in▁recorded▁unex▁areder▁st▁st▁automatic다에 stderr 행이 예기치 않게 기록됩니다.$Errorvariable(세션에서 발생한 모든 오류의 로그를 보관하는 변수) - 를 사용하는 경우에도2>$nullGitHub 문제를 참조하십시오.

    • 오류 : 추가되었는지 짧게 정리한 것은 오류 기록입니다.$Error.RemoveAt()하나씩, 하나도 없습니다.

일반적으로 안타깝게도 일부 PowerShell 호스트기본적으로 PowerShell의 오류 스트림을 통해 외부 프로그램의 stderr 출력라우팅합니다. 즉, 많은 외부 프로그램이 상태 정보에 stderr을 사용하거나 더 일반적으로 데이터가 아닌 모든 항목에 stderr을 사용하기 때문부적절합니다.git대표적인 예):모든 stderr 라인이 오류를 나타낸다고 가정할 수 없으며, stderr 출력이 있다고 해서 오류가 발생하는 것은 아닙니다.

영향을 받는 호스트:

  • 오래된 Windows PowerShell ISE 및 Visual Studio Code 이외의 다른 오래된 GUI 기반 IDE.

  • PowerShell 원격통해 또는 백그라운드 작업에서 외부 프로그램을 실행할 때(이 두 호출 메커니즘은 동일한 인프라를 공유하며ServerRemoteHostPowerShell과 함께 제공되는 호스트).

원격이 아닌 백그라운드가 아닌 호출에서 예상대로 동작하는 호스트(stderr 라인을 디스플레이에 전달하고 정상적으로 인쇄합니다.

이 GitHub 문제에서는 호스트 간의 이러한 불일치에 대해 설명합니다.

(참고: 대부분 추측입니다. PowerShell에서는 기본 명령을 거의 사용하지 않으며 PowerShell 내부에 대해 저보다 더 잘 알고 있는 사람도 있을 것입니다.)

PowerShell 콘솔 호스트에서 불일치를 발견한 것 같습니다.

  1. 하고 PowerShell을 .NativeCommandError.
  2. PowerShell은 표준 오류 스트림을 모니터링하는 경우에만 이를 선택할 수 있습니다.
  3. PowerShell ISE는 콘솔 애플리케이션이 아니므로 기본 콘솔 애플리케이션에 쓸 콘솔이 없기 때문에 이를 모니터링해야 합니다.이것이 PowerShell ISE에서 이것이 다음과 관계없이 실패하는 이유입니다.2>&1리디렉션 연산자.
  4. 사용하면 콘솔 호스트가 표준 오류 스트림을 모니터링합니다.2>&1표준 오류 스트림의 출력이 리디렉션되어 읽기 때문에 리디렉션 연산자입니다.

여기서는 콘솔 PowerShell 호스트가 게으르고 기본 콘솔이 출력을 처리할 필요가 없으면 콘솔 명령을 수동으로 실행할 수 있습니다.

PowerShell은 호스트 애플리케이션에 따라 다르게 동작하기 때문에 버그라고 생각합니다.

저에게는 ErrorAction Preference의 문제였습니다.ISE에서 실행할 때 첫 번째 줄에 $ErrorActionPreference = "Stop"을 설정했고 호출에 매개 변수로 *>&1이 추가된 모든 이벤트를 가로채는 것이었습니다.

그래서 처음에 저는 다음과 같은 대사를 했습니다.

& $exe $parameters *>&1

이전에 $ErrorActionPreference = "Stop" 파일이 있었기 때문에 작동하지 않았다고 말씀드렸듯이, (또는 스크립트를 시작하는 사용자를 위해 프로필에서 전체적으로 설정할 수 있습니다).

그래서 오류 액션을 강제로 적용하기 위해 Invoke-Expression으로 포장하려고 했습니다.

Invoke-Expression -Command "& `"$exe`" $parameters *>&1" -ErrorAction Continue

그리고 이것 또한 효과가 없습니다.

그래서 저는 일시적으로 오버라이드되는 ErrorAction Preference:

$old_error_action_preference = $ErrorActionPreference

try
{
    $ErrorActionPreference = "Continue"
    & $exe $parameters *>&1
}
finally
{
    $ErrorActionPreference = $old_error_action_preference
}

그게 나한테 효과가 있어요.

그리고 저는 그것을 함수로 포장했습니다.

<#
    .SYNOPSIS

    Executes native executable in specified directory (if specified)
    and optionally overriding global $ErrorActionPreference.
#>
function Start-NativeExecutable
{
    [CmdletBinding(SupportsShouldProcess = $true)]
    Param
    (
        [Parameter (Mandatory = $true, Position = 0, ValueFromPipelinebyPropertyName=$True)]
        [ValidateNotNullOrEmpty()]
        [string] $Path,

        [Parameter (Mandatory = $false, Position = 1, ValueFromPipelinebyPropertyName=$True)]
        [string] $Parameters,

        [Parameter (Mandatory = $false, Position = 2, ValueFromPipelinebyPropertyName=$True)]
        [string] $WorkingDirectory,

        [Parameter (Mandatory = $false, Position = 3, ValueFromPipelinebyPropertyName=$True)]
        [string] $GlobalErrorActionPreference,

        [Parameter (Mandatory = $false, Position = 4, ValueFromPipelinebyPropertyName=$True)]
        [switch] $RedirectAllOutput
    )

    if ($WorkingDirectory)
    {
        $old_work_dir = Resolve-Path .
        cd $WorkingDirectory
    }

    if ($GlobalErrorActionPreference)
    {
        $old_error_action_preference = $ErrorActionPreference
        $ErrorActionPreference = $GlobalErrorActionPreference
    }

    try
    {
        Write-Verbose "& $Path $Parameters"

        if ($RedirectAllOutput)
            { & $Path $Parameters *>&1 }
        else
            { & $Path $Parameters }
    }
    finally
    {
        if ($WorkingDirectory)
            { cd $old_work_dir }

        if ($GlobalErrorActionPreference)
            { $ErrorActionPreference = $old_error_action_preference }
    }
}

언급URL : https://stackoverflow.com/questions/10666101/lastexitcode-0-but-false-in-powershell-redirecting-stderr-to-stdout-gives

반응형