PowerShell 스코프: 변수 스코프 이해하기

PowerShell 스크립트를 작성할 때 함수가 없고 다른 스크립트에 대한 외부 종속성이 없다면 PowerShell 스코프의 개념은 크게 신경 쓸 필요가 없습니다. PowerShell 전역 변수의 개념은 중요하지 않습니다. 그러나 함수를 작성하고 모듈을 작성하며 다른 스크립트에서 스크립트를 호출하는 방법을 배우면 이 주제가 더욱 중요해집니다.

이 글에서는 PowerShell의 스코프가 무엇이며 어떻게 작동하는지, 스코프를 염두에 두고 코드를 작성하는 방법에 대해 깊게 알아보겠습니다. 읽기를 마치면 PowerShell의 전역 변수와 그 밖의 많은 내용을 이해할 수 있을 것입니다!

스코프: 어떤 면에서 버킷과 비슷합니다

변수를 정의한 후에도 해당 변수의 값이 다른 값으로 변경되는 스크립트를 작성해본 적이 있나요? 명확히 정의했음에도 불구하고 변수가 변경된 이유에 머리를 스크래치하실 수 있습니다. 그 이유 중 하나는 해당 변수의 값이 다른 스코프에서 덮어쓰여지고 있을 수 있습니다.

어떤 PowerShell 변수가 콘솔에서 참조하면 값이 있지만 스크립트에는 존재하지 않는 것에 대해 궁금해 해보셨나요? 아마 그 변수는 그 때 사용할 수 없는 다른 ‘버킷’에 있을 가능성이 있습니다.

스코프는 버킷과 같습니다. 스코프는 PowerShell이 변수, 별칭, 함수 및 PSDrive를 서로 다른 영역간에 어떻게 격리시키는지에 영향을 미칩니다. 스코프는 버킷 같은 곳입니다. 이러한 항목들을 모두 모아놓을 수 있는 장소입니다.

PowerShell이 시작될 때 이미 이러한 “버킷”을 자동으로 생성합니다. 그 순간에 이미 스코프를 사용하고 있지만 이를 인식하지 못하고 있습니다. 모든 스코프는 PowerShell에 의해 정의되며 당신의 도움 없이 자동으로 생성됩니다.

범위 유형

PowerShell이 시작되면, 자동으로 다양한 항목이 배치되는 네 개의 “버킷” 또는 범위를 생성합니다. 스코프를 직접 생성할 수는 없습니다. 아래에 정의된 이러한 스코프에 항목을 추가하거나 제거할 수만 있습니다.

전역 범위

PowerShell이 열릴 때 정의된 항목은 전역 범위에 설정됩니다. 이러한 항목에는 PowerShell 드라이브와 PowerShell 프로필에서 정의한 항목도 포함됩니다. 프로필은 시작할 때 실행되므로 프로필에서 정의한 항목도 이곳에 포함됩니다.

전역 범위의 항목은 어디에서나 사용할 수 있습니다. 전역 범위의 항목은 콘솔에서 상호작용하거나 실행하는 모든 스크립트 및 함수에서 참조할 수 있습니다. PowerShell의 전역 변수는 어디에서나 사용할 수 있습니다. 이러한 이유로, PowerShell 전역 변수의 일반적인 사용은 스크립트 간에 PowerShell 전역 변수를 사용하는 것입니다.

전체적으로 하나의 전역 범위만 존재합니다.

스크립트 범위

A script scope is automatically created every time a PowerShell script runs. You can have many different script scope instances. Script scopes are created when you execute a PS1 script or a module, for example.

해당 스크립트 범위 인스턴스에서만 생성된 항목들은 서로 참조할 수 있습니다.

개인 범위

일반적으로 정의된 항목은 다른 범위에서 접근할 수 있지만, 개인 범위에 있는 항목은 그렇지 않습니다. 개인 범위에는 다른 범위에서 숨겨진 객체를 포함합니다. 개인 범위는 중복을 방지하기 위해 다른 범위에 있는 항목과 동일한 이름의 항목을 생성하는 데 사용됩니다.

로컬 범위

다른 스코프와는 달리, 로컬 스코프는 약간 다릅니다. 로컬 스코프는 전역, 스크립트 또는 개인 스코프를 가리킵니다. 로컬 스코프는 코드가 실행되는 시점의 컨텍스트에 상대적입니다.

변수, 별칭, 함수 또는 PSDrive를 명시적으로 스코프에 할당하지 않으면 (나중에 다룰 것입니다) 로컬 스코프에 들어갑니다.

스코프 참조

이제 네 가지 스코프 유형에 대한 개념이 생겼으므로, 이러한 스코프를 참조하는 두 가지 방법도 알아야 합니다.

PowerShell에서는 스코프를 참조하기 위해 명명된 방법과 번호를 사용하는 두 가지 방법이 있습니다. 이 두 가지 방법은 동일한 스코프를 참조하지만 다른 방식으로 참조하는 것입니다. 이것은 스코프와 상호 작용하는 두 가지 다른 방법입니다.

명명된 스코프

스코프 유형 섹션에서 이름으로 참조되는 스코프에 대해 배웠습니다. 이름으로 스코프를 참조하는 것은 직관적으로 명명된 스코프라고 합니다. 이름으로 스코프를 참조하는 주된 목적은 항목을 스코프에 할당하는 것입니다. 아래에서 이를 배우게 될 것입니다.

번호가 매겨진 스코프

각 스코프에는 0부터 시작하는 번호가 있으며, 항상 로컬 스코프가 됩니다. 스코프는 현재 로컬 스코프와 관련하여 동적으로 번호가 매겨집니다.

예를 들어, PowerShell 세션을 열면 전역 스코프에서 작업합니다. 이 시점에서 전역 스코프가 로컬 스코프입니다 (로컬 스코프는 단지 포인터임을 기억하세요).

로컬 범위는 항상 범위 0이므로 현재 글로벌 범위도 범위 0입니다. 그러나 동일한 세션에서 스크립트를 실행하면 스크립트 범위가 생성됩니다. 실행 중에 로컬 범위 포인터는 스크립트 범위로 변경됩니다. 이제 스크립트 범위는 범위 0이 되고 글로벌 범위는 범위 1이 됩니다.

범위는 번호가 매겨집니다. 이 프로세스는 로컬 범위가 0인 범위가 있는 한 반복됩니다. 범위는 범위 계층에 따라 동적으로 번호가 매겨집니다.

범위 계층과 상속

이전에 언급한 대로 PowerShell 세션을 시작하면 PowerShell이 글로벌 범위에 항목을 생성합니다. 이러한 항목은 함수, 변수, 별칭 또는 PSDrive일 수 있습니다. PowerShell 세션에서 정의하는 모든 것은 글로벌 범위에도 정의됩니다.

기본적으로 글로벌 범위에 있으므로 스크립트 실행이나 함수 실행과 같이 다른 범위를 생성하는 작업을 수행하면 부모가 글로벌 범위인 자식 범위가 생성됩니다. 범위는 부모와 자식을 가진 프로세스와 같습니다.

부모 범위인 글로벌 범위에 정의된 항목은 자식 범위에서 액세스할 수 있습니다. 그러나 이러한 항목은 정의된 범위에서만 편집할 수 있습니다.

예를 들어 Test.ps1이라는 스크립트가 있다고 가정해 봅시다. 이 스크립트 내부에는 아래와 같은 한 줄이 있습니다.

$a = 'Hello world!'

이 스크립트를 실행하면 로컬 스코프(스크립트가 실행되는 동안)에서 $a에 값이 할당됩니다. Test.ps1이 실행되면 스크립트 실행 후에는 이를 참조할 수 없습니다. $a가 스크립트 스코프에서 할당되었기 때문에 대화형 콘솔의 전역 스코프에서 볼 수 없습니다.

Global scope cannot see the variable

이 예제를 한 단계 더 나아가서 Test.ps1 스크립트를 아래와 같이 만들어 보겠습니다. 스크립트는 이제 동일한 스코프에서 $a의 값을 설정하기 전에 출력하려고 시도합니다.

Write-Output $a
$a = 'Hello world!'

예를 들어, 대화형 콘솔에서 $a에 값을 할당해 보겠습니다. 이렇게 하면 전역 스코프에서 값이 할당됩니다. 이제 스크립트가 실행되면 부모 스코프(전역)를 상속하고 값을 볼 수 있어야 합니다.

Test.ps1이 실행될 때 (전역 스코프의 하위 스코프를 생성함) $a의 값을 볼 수 있습니다. 변수의 값이 설정된 전역 스코프에서도 사용 가능한 것을 볼 수 있습니다. 즉, $a는 스크립트(하위) 스코프와 부모(전역) 스코프 모두에서 사용 가능합니다.

Variable is available in script and global scopes

이러한 스코프 상속 동작을 기억해 두세요. 이렇게 함으로써 변수 충돌과 같은 문제가 발생할 때 해결하는 데 도움이 될 것입니다.

스코프에서 항목 정의 및 액세스

이제 스코프가 무엇이며 어떻게 작동하는지 알았으니, 어떻게 접근하는지 알아보겠습니다. PowerShell을 사용하여 변수 스코프를 설정하고 접근하는 방법을 살펴보겠습니다.

Get/Set-Variable

PowerShell에서는 Get-VariableSet-Variable이라는 두 개의 cmdlet을 사용하여 변수를 설정할 수 있습니다. 이러한 cmdlet을 사용하면 변수의 값을 검색하거나 값을 정의할 수 있습니다.

두 cmdlet은 NameScope 매개변수를 가지고 있습니다. 이러한 매개변수를 사용하여 PowerShell은 모든 스코프에서 변수 값을 설정하고 검색할 수 있도록 합니다.

로컬 스코프

로컬 스코프에서 변수를 설정하려면 아래와 같이 Set-Variable을 사용하여 로컬 변수 이름과 값을 제공하면 됩니다.

PS> Set-Variable -Name a -Value 'foo'

로컬 스코프는 항상 기본값이므로 Scope 매개변수를 사용하지 않으면 변수가 항상 로컬 스코프에 정의됩니다.

로컬 스코프 변수의 값을 검색하려면 이름을 지정하여 Get-Variable을 사용하면 됩니다.

PS> Get-Variable -Name a

Name         Value
----         -----
a            foo

개인/스크립트/전역 스코프

개인, 스크립트, 전역 변수를 사용할 때도 동일한 매개변수(NameValue)를 사용합니다. 유일한 차이점은 이번에는 Scope 매개변수를 사용하여 스코프를 명시적으로 정의해야 한다는 것입니다.

개인, 스크립트, 전역 스코프 변수를 설정하는 방법은 동일합니다. Scope 매개변수에 전달된 값으로 Private, Script, Global을 대체하면 됩니다.

PS> Set-Variable -Name a -Value 'foo' -Scope <Private|Script|Global>

스크립트나 전역 범위 변수의 값을 검색하려면 이름과 범위를 제공하여 Get-Variable을 사용합니다.

PS> Get-Variable -Name a -Scope <Script|Global>

Name         Value
----         -----
a            foo

참고: Get/Set-Variable cmdlet을 사용하여 이름 대신 범위 번호를 사용하여 범위를 참조할 수도 있습니다.

범위 앞에 접두사 사용

변수를 참조할 때 PowerShell cmdlet을 사용하는 대신 변수 앞에 범위를 접두사로 붙일 수도 있습니다.

로컬 범위

로컬 범위는 항상 기본값이므로 변수를 정의하고 참조하면 로컬 범위 변수가 설정되고 검색됩니다.

PS> $a = 'foo'
PS> $a

foo

개인/스크립트/전역 범위

스크립트나 전역 범위 변수를 정의하고 참조하려면 범위 이름과 세미콜론을 접두사로 붙일 수 있습니다.

예를 들어, 전역 범위에 변수 $a를 설정하려면 $global:와 함께 a를 접두사로 붙일 수 있습니다.

$global:a = ‘foo’

스크립트 범위 변수에도 같은 방식으로 할 수 있습니다.

$script:a = ‘foo’

선호하는 범위에 변수가 설정되면 동일한 방식으로 참조할 수 있습니다. 또한, 정의된 범위가 로컬 범위인 경우 범위 접두사를 생략할 수 있다는 점에 유의하세요.

PS> $global:a = 'foo'
PS> $global:a
foo
PS> $a
foo

스크립트 블록의 범위

PowerShell에는 scriptblock이라는 편리한 구조가 있습니다. Scriptblock을 사용하면 코드 일부를 이동하고 거의 어디에서나 실행할 수 있습니다.

PS1 스크립트와 마찬가지로 scriptblock은 자체 스크립트 범위에서 실행됩니다. scriptblock을 실행하면 PS1 스크립트를 실행하는 것과 동일합니다.

다음 예제에서 전역 범위에 변수를 정의한 후에 스크립트 범위에서 덮어쓰려고 시도하는 곳에 주목하세요. 앞서 배운대로, 자식 범위는 부모 범위를 참조할 수 없으므로 이 작업은 작동하지 않습니다.

A child scope cannot overwrite a parent scope

이 예제는 스크립트 블록에서 $a가 변경되었을 때 전역 변수 정의인 $a가 변경되지 않는 것을 보여줍니다. 이는 스크립트 블록이 스크립트 자식 범위이기 때문입니다.

스크립트 도트 소싱(로컬 범위 교체)

PowerShell에는 도트 소싱이라는 개념이 있습니다. 이는 PS1 스크립트를 실행하고 스크립트 범위에 있을 것으로 예상되는 모든 것을 로컬 범위로 가져오는 방법입니다.

PS1 스크립트를 참조하기 전에 점 (.)을 붙여서 실행하면 이 스크립트의 내용이 “도트 소싱”되어 로컬 범위로 모두 가져와집니다.

증명하기 위해, 다시 Test.ps1 스크립트가 있는데, 이는 아래와 같이 변수를 정의합니다.

$a = 'Hello world!'

PowerShell 콘솔에서 $a 변수에 값을 설정한 다음 이 스크립트를 도트 소스로 참조하면 원래 변수가 덮어씌워짐을 알 수 있습니다. PowerShell은 두 로컬 범위를 “병합”했습니다.

Original variable overwritten

AllScope 속성 사용 (옵션)

특정 범위의 항목과 상호작용하는 방법을 이미 알아보았지만, 지금까지 각 항목은 여전히 단일 범위에 정의되어 있습니다. 그러나 변수가 어느 범위에 정의되어 있는지 모를 경우에는 어떻게 해야 할까요?

Set-Variable cmdlet을 사용하여 변수를 정의할 때, 한 번에 모든 스코프에 변수를 배치할 수 있습니다. 이를 위해 Option 매개변수에 AllScope 값을 사용하십시오.

예를 들어, Test.ps1 스크립트가 수정되어 모든 스코프에 변수를 설정하도록 변경되었습니다. 해당 변수는 아래와 같이 출력됩니다.

Set-Variable -Name a -Value 'Hello world!' -Option AllScope
Write-Output $a

그런 다음 아래에서 전역 스코프에 $a의 값을 설정하고 Test.ps1 스크립트를 실행합니다. 그러나 영향이 없어야 할 것이지만, $a의 값은 덮어쓰여집니다. 해당 변수는 스크립트 스코프(Write-Output $a)에 정의되었을 뿐만 아니라 전역 스코프도 덮어쓰게 됩니다.

Variable overwritten in global scope

AllScope 옵션은 편리하지만 조심해야 합니다. 이 옵션은 사실상 스코프의 개념을 제거하고 모든 것을 하나로 합칩니다.

함수 스코프

함수를 실행하면 해당 함수 내의 모든 코드가 독립적인 자식 스코프에 속합니다. 함수 스코프는 다른 스코프와 동일한 자식/부모 동작을 따릅니다.

각 함수마다 별도의 스코프를 가지는 것은 좋은 생각입니다. 이를 통해 충돌하는 항목에 대해 더 나은 제어를 할 수 있으며, 또한 함수 내에서 변수를 자동으로 정리하는 추가적인 이점을 제공합니다. 함수가 완료되면 함수에 정의된 모든 항목이 지워집니다.

아래의 함수를 PowerShell 콘솔에 직접 복사하여 붙여넣기하여 보여드리겠습니다.

Function Do-Thing {
    $var = 'bar'
}

붙여넣은 후에 함수를 실행하십시오. 함수 외부에서 $var 변수에 액세스할 수 없음을 확인하십시오.

PS> Do-Thing
PS> $var

항목 비공개 유지 (상속 비활성화)

일반적으로, 변수가 상위 범위에서 정의되면 해당 변수는 하위 범위에서도 정의됩니다. 그러나 아마도 변수 이름을 사용하고자 하지만 해당 변수 이름이 세션에서 실행되는 범위 중 하나에서 이미 정의되어 있는 경우, 다른 변수 이름을 선택하거나 해당 변수를 비공개 범위에 정의하여 비공개 변수로 만들 수 있습니다.

A way to use scopes to reduce conflicts is to use the private scope. Using the private scope disables inheritance on that specific variable. When multiple child scopes are created, those child scopes will not see any variables defined in a private scope.

A Test.ps1 script outputs the value of $a as shown below.

Write-Output $a

아래에서 볼 수 있듯이, 전역 범위에서 비공개 범위 변수를 정의하고 Test.ps1 스크립트를 실행하고 있습니다. 일반적으로 상위 범위에서 변수를 정의하면 해당 변수는 하위 범위에서 사용할 수 있지만, 비공개 범위 변수는 그렇지 않습니다.

아래 예제에서는 Test.ps1 스크립트를 실행하여 생성된 스크립트 하위 범위에서 상위 범위에 정의된 비공개 범위 $a 변수를 볼 수 없음을 확인할 수 있습니다.

Script scope cannot see the private-scoped variable

전역 범위나 Set-Variable cmdlet의 AllScope 옵션과 달리, 비공개 범위 변수는 항목을 구획화하는 뛰어난 방법입니다.

범위 관리 모범 사례

전역 범위에서 변수를 정의하거나 AllScope 옵션을 사용하는 것이 일반적입니다. 결국 모든 변수는 어디에서든 사용할 수 있습니다. 범위의 복잡성에 대해 걱정할 필요가 없습니다. 정의된 내용에 대한 추가적인 접근 권한을 제공하지만, 이는 빠르게 제어를 잃고 문제 해결이 어려워질 수 있습니다.

범위 사용을 방지하려고 하지 말고, 다음 팁을 따르세요:

  1. 함수에서 범위를 지정하는 대신, 매개변수를 사용하여 필요한 정보를 함수로 전달하세요.
  2. 가능한 한 로컬 범위 내에서 유지하세요.
  3. 스크립트에서 전역 변수를 정의하는 대신, Write-Output cmdlet을 사용하여 모든 것을 출력하고 필요할 때 콘솔에서 변수로 저장하세요.

주요 포인트는 범위를 받아들이고 회피하는 대신에 이를 자신의 이점으로 활용하는 것입니다.

더 읽어보기

Source:
https://adamtheautomator.com/powershell-scopes/