about_Trap — trap 키워드로 종료 오류 다루기

about_Trap — trap 키워드로 종료 오류 다루기

PowerShell에서 명령을 실행하다 보면 '종료 오류'라는 게 생겨요. 이 오류가 나면 그 명령문이 멈추게 되는데, 상황에 따라 현재 명령문만 멈추는지 스크립트 전체가 멈추는지가 달라져요. 바로 이 종료 오류를 가로채서 우리가 원하는 처리를 해 주는 키워드가 trap이에요. 이 문서에서 trap이 어떻게 동작하고, 언제 쓰면 좋은지 하나씩 정리해 드릴게요.

출처: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_trap

본문

짧은 설명

명령문을 멈추는 종료 오류(statement-terminating)와 스크립트 전체를 멈추는 종료 오류(script-terminating)를 처리하는 키워드를 설명해요.

자세한 설명

종료 오류는 명령문의 실행을 멈춰요. PowerShell은 현재 명령문만 멈추는 종료 오류와 전체 호출 스택을 풀어버리는 종료 오류를 서로 구분해요. trap 키워드는 이 두 종류를 모두 처리할 수 있어요. 오류 범주에 대해 더 자세히 알고 싶으면 about_Error_Handling 문서를 참고해 주세요.

trap 키워드는 종료 오류가 발생했을 때 실행할 명령문 목록을 지정해요. trap 문은 종료 오류를 다음과 같은 방식으로 처리할 수 있어요.

  • trap 명령 블록을 처리한 뒤 오류를 표시하고, trap이 들어 있는 스크립트나 함수의 실행을 계속 진행해요. 이게 기본 동작이에요. 참고로 종료 오류가 if 문이나 foreach 루프 같은 하위 명령 블록에서 발생하면, trap 블록의 명령이 실행된 뒤 그 하위 블록 밖의 다음 명령문부터 실행이 이어져요.
  • trap 문에서 break를 사용하면 오류를 표시하면서, trap이 들어 있는 스크립트나 함수의 실행을 중단해요.
  • trap 문에서 continue를 사용하면 오류를 표시하지 않되, trap이 들어 있는 스크립트나 함수의 실행은 계속 이어가요.

trap의 명령문 목록에는 여러 조건이나 함수 호출을 담을 수 있어요. trap은 로그를 남기고, 조건을 검사하고, 심지어 다른 프로그램을 실행할 수도 있어요.

구문

trap 문의 구문은 다음과 같아요.

trap [[<error type>]] {<statement list>}

trap 문에는 종료 오류가 발생했을 때 실행할 명령문 목록이 들어가요. trap 문은 trap 키워드, 그다음에 선택적으로 오는 타입 표현(형식 식), 그리고 오류가 잡혔을 때 실행할 명령문 목록이 담긴 명령 블록으로 구성돼요. 여기서 타입 표현은 trap이 잡을 오류의 종류를 좁혀 주는 역할을 해요.

하나의 스크립트나 명령에는 trap 문을 여러 개 둘 수 있어요. trap 문은 스크립트나 명령 안 어디에든 나타날 수 있어요.

모든 종료 오류 잡기

스크립트나 명령 안에서 다른 방식으로 처리되지 않은 종료 오류가 발생하면, PowerShell은 그 오류를 처리할 trap 문이 있는지 확인해요. trap 문이 있다면 PowerShell은 trap 문 안에서 스크립트나 명령의 실행을 이어가요.

다음은 가장 기본적인 trap 문의 예시예요.

trap { 'Error found.' }

trap 문은 어떤 종료 오류든 잡아내요.

다음 예시의 함수에는 런타임 오류를 일으키는 의미 없는 문자열이 들어 있어요.

function TrapTest {
    trap { 'Error found.' }
    nonsenseString
}

TrapTest

이 함수를 실행하면 다음과 같은 결과가 나와요.

Error found.
nonsenseString:
Line |
   3 |      nonsenseString
     |      ~~~~~~~~~~~~~~
     | The term 'nonsenseString' is not recognized as a name of a cmdlet,
function, script file, or executable program.
Check the spelling of the name, or if a path was included, verify that the
path is correct and try again.

다음 예시는 자동 변수 $_ 또는 $PSItem을 사용해 오류를 표시하는 trap 문을 담고 있어요.

function TrapTest {
    trap { "Error found: $_" }
    nonsenseString
}

TrapTest

이 버전의 함수를 실행하면 다음과 같은 결과가 나와요.

Error found: The term 'nonsenseString' is not recognized as the name of a
cmdlet, function, script file, or operable program. Check the spelling of
the name, or if a path was included, verify that the path is correct and
try again.
nonsenseString:
Line |
   3 |      nonsenseString
     |      ~~~~~~~~~~~~~~
     | The term 'nonsenseString' is not recognized as a name of a cmdlet,
function, script file, or executable program.
Check the spelling of the name, or if a path was included, verify that the
path is correct and try again.

중요

trap 문은 주어진 스크립트블록 안 어디에든 정의할 수 있지만, 항상 그 스크립트블록 안의 모든 명령문에 적용돼요. 런타임에서는 블록 안의 trap 문이 다른 어떤 명령문보다 먼저 정의된 것으로 처리돼요. 다른 언어, 예를 들어 JavaScript에서는 이걸 호이스팅이라고 불러요. 쉽게 말해 trap 문이 정의된 위치보다 앞에 있는 명령문에도 적용된다는 뜻이에요. 예를 들어 스크립트 맨 끝에 trap을 정의해 놓고 첫 번째 명령문에서 오류를 던져도 그 trap이 발동해요.

특정 오류 잡기

하나의 스크립트나 명령에는 trap 문을 여러 개 둘 수 있어요. 그리고 trap을 정의할 때 특정 오류만 처리하도록 지정할 수도 있어요.

다음은 특정 오류인 CommandNotFoundException을 잡는 trap 문의 예시예요.

trap [System.Management.Automation.CommandNotFoundException] {
    'Command error trapped'
}

함수나 스크립트가 알려진 명령과 일치하지 않는 문자열을 만나면, 이 trap 문이 Command error trapped라는 문자열을 표시해요. trap 명령문 목록을 실행한 뒤 PowerShell은 오류 객체를 오류 스트림에 기록하고 스크립트를 계속 진행해요.

PowerShell은 .NET 예외 형식을 사용해요. 다음 예시는 System.Exception 오류 형식을 지정하고 있어요.

trap [System.Exception] { 'An error trapped' }

CommandNotFoundException 오류 형식은 System.Exception 형식에서 상속받아요. 이 문은 알 수 없는 명령으로 생긴 오류를 잡아요. 물론 그 외 다른 오류 형식도 함께 잡아요.

오류의 예외 형식은 오류 객체를 살펴보면 알 수 있어요. 다음 예시는 세션에서 발생한 마지막 오류의 예외 전체 이름을 가져오는 방법을 보여 줘요.

nonsenseString
$Error[0].Exception.GetType().FullName
nonsenseString: The term 'nonsenseString' is not recognized as a name of a
cmdlet, function, script file, or executable program. Check the spelling
of the name, or if a path was included, verify that the path is correct
and try again.

System.Management.Automation.CommandNotFoundException

하나의 스크립트에 trap 문을 여러 개 둘 수 있어요. 다만 각 오류 형식은 하나의 trap 문만 잡을 수 있어요. 종료 오류가 발생하면 PowerShell은 실행 중인 현재 스크립트블록에서 시작해 가장 구체적으로 일치하는 trap을 찾아요.

다음 스크립트 예시에는 오류가 하나 들어 있어요. 이 스크립트에는 어떤 종료 오류든 잡는 일반 trap 문과, CommandNotFoundException 형식을 지정한 특정 trap 문이 함께 있어요.

trap { 'Other terminating error trapped' }
trap [System.Management.Automation.CommandNotFoundException] {
  'Command error trapped'
}
nonsenseString

이 스크립트를 실행하면 다음과 같은 결과가 나와요.

Command error trapped
nonsenseString:
Line |
   5 |      nonsenseString
     |      ~~~~~~~~~~~~~~
     | The term 'nonsenseString' is not recognized as a name of a cmdlet,
function, script file, or executable program.
Check the spelling of the name, or if a path was included, verify that the
path is correct and try again.

PowerShell은 nonsenseString을 cmdlet이나 그 밖의 항목으로 인식하지 못하기 때문에 CommandNotFoundException 오류를 반환해요. 이 종료 오류는 특정 trap 문이 잡아내요.

다음 스크립트 예시는 같은 trap 문을 다른 오류에 적용했어요.

trap { 'Other terminating error trapped' }
trap [System.Management.Automation.CommandNotFoundException] {
    'Command error trapped'
}
1/$null

이 스크립트를 실행하면 다음과 같은 결과가 나와요.

Other terminating error trapped
RuntimeException:
Line |
   5 |  1/$null
     |  ~~~~~~~
     | Attempted to divide by zero.

0으로 나누려는 시도는 CommandNotFoundException 오류를 만들지 않아요. 대신 어떤 종료 오류든 잡는 다른 trap 문이 이 0 나눗셈 오류를 잡아요.

스크립트블록 안에서 오류 잡기

기본적으로 종료 오류가 발생하면 실행이 trap 문으로 넘어가요. trap 블록이 실행된 뒤에는, 오류가 발생한 위치 다음에 있는 명령 블록으로 제어가 되돌아가요.

예를 들어 foreach 문 안에서 종료 오류가 발생하면, trap 문이 실행되고 나서 실행은 foreach 블록 안이 아니라 foreach 블록 밖의 다음 명령문에서 이어져요.

trap { 'An error occurred!'}
foreach ($x in 3..-1) {
       "1/$x = "
       "`t$(1/$x)"
}
'after loop'
1/3 =
        0.333333333333333
1/2 =
        0.5
1/1 =
        1
1/0 =
An error occurred!
RuntimeException:
Line |
   4 |         "`t$(1/$x)"
     |              ~~~~
     | Attempted to divide by zero.
after loop

출력을 보면 루프가 마지막 반복까지 계속 진행되는 걸 확인할 수 있어요. 스크립트가 1을 0으로 나누려 할 때 PowerShell이 종료 오류를 던져요. 그러면 스크립트는 foreach 문의 나머지를 건너뛰고 trap 문을 실행한 뒤, foreach 문 다음에서 실행을 이어가요.

오류 잡기와 범위(스코프)

trap 문과 오류가 같은 스크립트블록 안에 있으면, PowerShell은 trap에 정의된 명령문 목록을 실행해요. 그리고 오류 다음에 있는 문장부터 실행을 이어가요. 반대로 trap 문이 오류와 다른 스크립트블록에 있다면, 실행은 trap 문과 같은 스크립트블록에 있는 다음 문장에서 이어져요.

예를 들어 함수 안에서 오류가 발생하고 trap 문도 그 함수 안에 있다면, 스크립트는 다음 문장에서 계속 실행돼요. 다음 스크립트에는 오류와 trap 문이 하나씩 들어 있어요.

function function1 {
    trap { 'An error: ' }
    NonsenseString
    'function1 was completed'
}

function1

이 스크립트를 실행하면 다음과 같은 결과가 나와요.

An error:
NonsenseString:
Line |
   3 |      NonsenseString
     |      ~~~~~~~~~~~~~~
     | The term 'NonsenseString' is not recognized as a name of a cmdlet,
function, script file, or executable program.
Check the spelling of the name, or if a path was included, verify that the
path is correct and try again.
function1 was completed

함수 안의 trap 문이 오류를 잡아요. 메시지를 표시한 뒤 PowerShell은 함수 실행을 다시 시작해요. Function1trap 문 다음에 완료된 점에 주목해 주세요.

이 동작을 같은 오류와 trap 문을 쓰지만 trap 문이 함수 밖에 있는 다음 예시와 비교해 볼게요.

function function2 {
    NonsenseString
    'function2 was completed'
}

trap { 'An error:' }

function2

Function2 함수를 실행하면 다음과 같은 결과가 나와요.

An error:
NonsenseString:
Line |
   2 |      NonsenseString
     |      ~~~~~~~~~~~~~~
     | The term 'NonsenseString' is not recognized as a name of a cmdlet,
function, script file, or executable program.
Check the spelling of the name, or if a path was included, verify that the
path is correct and try again.

이 예시에서는 function2 was completed 명령이 실행되지 않았어요. 두 예시 모두 함수 안에서 종료 오류가 발생했죠. 다만 이 예시에서는 trap 문이 함수 밖에 있어요. 그래서 trap 문이 실행된 뒤 PowerShell은 함수 안으로 되돌아가지 않아요.

주의

같은 오류 조건에 대해 trap이 여러 개 정의되면, 어휘적으로(스크립트블록 안에서 가장 위에) 먼저 정의된 trap이 사용돼요.

다음 예시에서는 whoops 1이 들어 있는 trap만 실행돼요.

Remove-Item -ErrorAction Stop ThisFileDoesNotExist
trap { 'whoops 1'; continue }
trap { 'whoops 2'; continue }

중요

trap 문은 컴파일되는 위치를 기준으로 범위가 정해져요. 함수나 닷소스(dot-sourced) 스크립트 안에 trap 문이 있으면, 그 함수나 닷소스 스크립트가 종료될 때 내부의 trap 문은 모두 사라져요.

break와 continue 키워드 사용하기

trap 문 안에서 breakcontinue 키워드를 사용하면, 종료 오류 이후에 스크립트나 명령의 실행을 계속할지 정할 수 있어요.

trap 문 목록에 break 문을 넣으면 PowerShell이 함수나 스크립트를 멈춰요. 다음 예시 함수는 trap 문에서 break 키워드를 사용해요.

function break_example {
    trap {
        'Error trapped'
        break
    }
    1/$null
    'Function completed.'
}

break_example
Error trapped
ParentContainsErrorRecordException:
Line |
   6 |      1/$null
     |      ~~~~~~~
     | Attempted to divide by zero.

trap 문에 break 키워드가 들어 있어서 함수가 계속 실행되지 않고, Function completed 줄도 실행되지 않아요.

trap 문에 continue 키워드를 넣으면, PowerShell은 breakcontinue가 없을 때처럼 오류를 일으킨 문장 다음에서 실행을 재개해요. 다만 continue 키워드를 쓰면 PowerShell은 오류를 오류 스트림에 기록하지 않아요.

다음 예시 함수는 trap 문에서 continue 키워드를 사용해요.

function ContinueExample {
    trap {
        'Error trapped'
        continue
    }
    foreach ($x in 3..-1) {
       "1/$x = "
       "`t$(1/$x)"
    }
    'End of function'
}

ContinueExample
1/3 =
        0.333333333333333
1/2 =
        0.5
1/1 =
        1
1/0 =
Error trapped
End of function

이 함수는 오류가 잡힌 뒤 실행을 재개해서 End of function 문장이 실행돼요. 그리고 오류 스트림에는 어떤 오류도 기록되지 않아요.

참고

trap 문은 스크립트블록 안의 모든 종료 오류가 처리되도록 보장하는 방법을 제공해요. 더 세밀하게 오류를 처리하고 싶다면 try/catch 블록을 쓰는 게 좋아요. 이때 catch 문으로 trap을 정의하면 되죠. catch 문은 관련된 try 문 안의 코드에만 적용돼요. 자세한 내용은 about_Try_Catch_Finally 문서를 참고해 주세요.

더 알아보기