Escopos do PowerShell: Compreendendo o Escopo de Variáveis

Quando você escreve um script PowerShell sem funções e sem dependências externas de outros scripts, o conceito de escopos do PowerShell não é muito importante. O conceito de variáveis globais do PowerShell não está em primeiro plano. Mas à medida que você começa a construir funções, módulos e aprende a chamar scripts de outros scripts, o assunto se torna mais importante.

Neste artigo, você vai aprender profundamente o que são escopos no PowerShell, como eles funcionam e como você pode escrever código levando em consideração os escopos. Quando terminar, você entenderá as variáveis globais do PowerShell e muito mais!

Escopos: Mais ou Menos como Baldes

Você já escreveu um script em que define uma variável e, quando verifica o valor dessa variável, é algo diferente? Você pode se perguntar como aquela variável mudou quando você a definiu claramente. Uma das razões pode ser que o valor da variável está sendo sobrescrito em outro escopo.

Talvez você já tenha se perguntado como certas variáveis do PowerShell têm valores quando você as referencia no console, mas não existem em seus scripts. É provável que essas variáveis estejam em outro “balde” que não está disponível naquele momento.

Os escopos são como baldes. Os escopos afetam a maneira como o PowerShell isola variáveis, aliases, funções e PSDrives entre diferentes áreas. Um escopo é como um balde. É um lugar para coletar todos esses itens juntos.

Quando o PowerShell é iniciado, ele cria automaticamente esses “baldes” para você. Nesse ponto, você já está usando escopos sem perceber. Todos os escopos são definidos pelo PowerShell e são criados sem qualquer ajuda de sua parte.

Tipos de Escopo

Ao iniciar o PowerShell, ele automaticamente cria quatro “buckets” ou escopos para vários itens serem colocados. Você não pode criar escopos por conta própria. Você só pode adicionar e remover itens desses escopos definidos abaixo.

Escopo Global

Itens definidos quando o PowerShell é aberto são configurados no escopo global. Esses itens incluem objetos criados pelo sistema, como unidades do PowerShell e também qualquer coisa que você tenha definido em um perfil do PowerShell desde que seu perfil seja executado na inicialização.

Itens no escopo global estão disponíveis em todos os lugares. Você pode referenciar itens no escopo global interativamente no console, em qualquer script que você execute e em qualquer função. As variáveis globais do PowerShell estão em toda parte. Por esse motivo, o uso comum de variáveis globais do PowerShell é utilizar variáveis globais do PowerShell entre scripts.

Há apenas um escopo global que rege tudo.

Escopo de Script

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.

Apenas itens criados naquela instância específica de escopo de script podem se referenciar entre si.

Escopo Privado

Normalmente, um item definido pode ser acessado de outros escopos – o que não é verdade com itens em um escopo privado. Itens em um escopo privado contêm objetos que estão ocultos para outros escopos. Um escopo privado é usado para criar itens com o mesmo nome que itens em outros escopos para evitar sobreposição.

Escopo Local

Ao contrário dos outros escopos, o escopo local é um pouco diferente. O escopo local é um ponteiro para o escopo global, de script ou privado. O escopo local é relativo ao contexto em que o código é executado no momento.

Se você criar uma variável, alias, função ou PSDrive sem atribuir explicitamente um escopo (o que veremos mais adiante), ela será atribuída ao escopo local.

Referenciando Escopos

Agora que você tem uma ideia dos quatro tipos de escopos que existem, você também deve saber que existem duas maneiras de referenciar esses escopos.

No PowerShell, existem duas maneiras de referenciar os escopos – por nome ou por número. Ambos os métodos referenciam os mesmos escopos, mas de maneiras diferentes. Essas são duas maneiras diferentes de interagir com os escopos.

Escopos Nomeados

Acima na seção Tipo de Escopo, você aprendeu sobre os escopos referenciados por nome. Referenciar um escopo pelo nome é, intuitivamente, chamado de escopo nomeado. O principal propósito de referenciar um escopo pelo nome é atribuir um item a um escopo. Você aprenderá como fazer isso abaixo.

Escopos Numerados

Além de um nome, cada escopo tem um número começando em zero, que sempre será o escopo local. Os escopos são numerados dinamicamente em relação ao escopo local atual.

Por exemplo, assim que você abre uma sessão do PowerShell, você está operando no escopo global. Nesse ponto, o escopo global é o escopo local (lembre-se de que o escopo local é apenas um ponteiro).

Uma vez que o escopo local é sempre escopo zero, neste ponto, o escopo global também está atualmente como escopo zero. Porém, quando você executa um script a partir dessa mesma sessão, um escopo de script é criado. Durante a execução, o ponteiro do escopo local é então alterado para o escopo do script. Agora, o escopo do script é escopo zero e o escopo global é escopo um.

Os escopos são numerados. Esse processo se repete para quantos escopos você tiver, onde o escopo local é 0. Os escopos são numerados dinamicamente pela hierarquia de escopo.

Hierarquia de Escopo e Herança

Como mencionado anteriormente, quando você inicia uma sessão do PowerShell, o PowerShell cria alguns itens para você no escopo global. Esses itens podem ser funções, variáveis, aliases ou PSDrives. Qualquer coisa que você defina em sua sessão do PowerShell também será definida no escopo global.

Já que você está no escopo global por padrão, se você fizer algo que crie outro escopo, como executar um script ou executar uma função, um escopo filho será criado, com o pai sendo o escopo global. Os escopos são como processos com pais e filhos.

Tudo o que é definido em um escopo pai, o escopo global, neste caso, será acessível no escopo filho. Mas esses itens só são editáveis no escopo em que foram definidos.

Vamos dizer que você tenha um script chamado Test.ps1. Dentro deste script, há uma única linha conforme mostrado abaixo.

$a = 'Hello world!'

Quando você executa este script, $a é atribuído um valor no escopo local (enquanto o script está em execução). Quando Test.ps1 é executado, você pode ver abaixo que não é possível fazer referência a ele depois que o script é executado. Como $a foi atribuído enquanto estava no escopo do script, o escopo global (no console interativo) não consegue vê-lo.

Global scope cannot see the variable

Vamos levar este exemplo um passo adiante e fazer o script Test.ps1 parecer assim abaixo. O script está agora tentando exibir o valor de $a antes de defini-lo no mesmo escopo.

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

Para demonstrar, atribua um valor a $a no console interativo. Isso atribui o valor no escopo global. Agora, quando o script é executado, ele herdará o escopo pai (global) e deverá ser capaz de ver o valor.

Você pode ver abaixo que quando Test.ps1 é executado (criando um escopo filho do escopo global), ele pode ver o valor de $a. Você também pode ver que o valor da variável está disponível também no escopo global, já que este é o escopo onde foi definido. Isso significa que $a está disponível tanto nos escopos do script (filho) quanto do pai (global).

Variable is available in script and global scopes

Lembre-se deste comportamento de herança de escopo. Fazê-lo ajudará você quando ocorrerem ocasiões de solução de problemas, como conflitos de variáveis em escopos diferentes com o mesmo nome.

Definindo e Acessando Itens em Escopos

Agora que você sabe o que é um escopo e como eles funcionam, como você acessa-los? Vamos ver como usar o PowerShell para definir um escopo de variável (e acessá-los).

Obter/Definir-Variável

No PowerShell, existem dois cmdlets que permitem definir variáveis chamados Get-Variable e Set-Variable. Esses cmdlets permitem que você obtenha o valor de uma variável ou defina um valor.

Ambos os cmdlets são semelhantes com um parâmetro Nome e Escopo. Usando esses parâmetros, o PowerShell permite que você defina e recupere valores de variáveis em todos os escopos.

Escopos Locais

Para definir uma variável em um escopo local, use Set-Variable e forneça um nome de variável local e um valor conforme mostrado abaixo.

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

O escopo local é sempre o padrão, então não usar o parâmetro Escopo sempre definirá a variável no escopo local.

Para recuperar o valor de uma variável de escopo local, use Get-Variable fornecendo o nome.

PS> Get-Variable -Name a

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

Escopos Privados/Script/Globais

Você usará os mesmos parâmetros (Nome e Valor) ao trabalhar com variáveis privadas, de script e globais também. A única diferença é que desta vez você usará o parâmetro de Escopo para definir explicitamente o escopo.

Os métodos para definir uma variável de escopo privado, de script ou global são os mesmos. Simplesmente substitua o valor passado para o parâmetro Escopo por Privado, Script, Global.

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

Para recuperar o valor de um script ou variável de escopo global, use Get-Variable fornecendo o nome e o escopo.

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

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

Nota: Você também pode referenciar escopos usando números de escopo em vez de nomes com os cmdlets Get/Set-Variable.

Escopo “Preferencial”

Também é possível recuperar e definir variáveis em escopos usando um atalho. Em vez de usar cmdlets do PowerShell, você irá anteceder o escopo à variável ao referenciá-la.

Escopos Locais

Como o escopo local é sempre o padrão, simplesmente definir uma variável e referenciá-la irá definir e recuperar uma variável de escopo local

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

foo

Escopos Privados/Script/Global

Se você deseja definir e referenciar variáveis de script ou globais, pode anteceder as variáveis com o nome do escopo e um ponto e vírgula.

Por exemplo, para definir a variável $a no escopo global, você pode antecedê-la com $global:.

$global:a = ‘foo’

O mesmo pode ser feito para uma variável de escopo de script.

$script:a = ‘foo’

Uma vez que as variáveis são definidas no escopo preferencial, você então as referencia da mesma maneira. Além disso, observe que você pode excluir o prefácio do escopo se o escopo definido for o escopo local.

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

Escopos em Blocos de Script

O PowerShell tem uma construção útil chamada blocos de script. Os blocos de script permitem que você mova trechos de código e os execute em praticamente qualquer lugar.

Como um script PS1, os blocos de script são executados em seu próprio escopo de script. Quando você executa um bloco de script, essencialmente está executando um script PS1.

Observe no exemplo abaixo onde uma variável é definida no escopo global e depois tenta ser sobrescrita no escopo de script. Como você aprendeu acima, isso não funcionará porque um escopo filho não pode referenciar um escopo pai.

A child scope cannot overwrite a parent scope

Este exemplo mostra que quando $a é alterado no bloco de script, a definição de variável global para $a não é alterada porque o bloco de script é um escopo filho de script.

Dot Sourcing Scripts (Troca de Escopos Locais)

O PowerShell tem um conceito chamado dot-sourcing. Este é um método que permite executar um script PS1 e trazer tudo o que seria de escopo de script para o escopo local em vez disso.

Ao colocar um ponto (.) antes de referenciar um script PS1 e executá-lo, isso “dot sources” o conteúdo do script e traz tudo para o escopo local.

Para demonstrar, tenho novamente um script Test.ps1 que define uma variável conforme mostrado abaixo.

$a = 'Hello world!'

Em um console do PowerShell, defina um valor para uma variável $a e depois dot source este script conforme mostrado abaixo. Observe que a variável original foi sobrescrita. O PowerShell “fundiu” os dois escopos locais juntos.

Original variable overwritten

Usando a Propriedade AllScope (Opção)

Você viu como interagir com itens em escopos específicos, mas até agora cada item ainda está definido em um único escopo. Mas e se você não souber em qual escopo uma variável está definida?

Quando você define uma variável com o cmdlet Set-Variable, você pode colocar a variável em todos os escopos de uma vez. Para fazer isso, use o valor AllScope para o parâmetro Option.

Para demonstrar, o script Test.ps1 foi modificado para definir uma variável em todos os escopos. Essa variável é então exibida como mostrado abaixo.

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

Em seguida, você pode ver abaixo que um valor está sendo definido para $a no escopo global e o script Test.ps1 é executado. No entanto, em vez de não ter efeito, o valor de $a foi sobrescrito. Não só foi definido no escopo do script (Write-Output $a), mas também sobrescreveu o escopo global.

Variable overwritten in global scope

A opção AllScope é útil, mas tome cuidado. Essa opção essencialmente elimina o conceito de escopos e mescla tudo.

Escopos de Função

Ao executar uma função, todo o código dentro dessa função está em seu próprio escopo filho. Os escopos de função seguem o mesmo comportamento filho/pai que outros escopos.

Ter escopos separados para cada função é uma boa ideia. Isso permite um melhor controle sobre os itens sem se preocupar com conflitos. Também oferece o benefício adicional de limpeza automatizada de variáveis em uma função. Assim que a função é concluída, todos os itens definidos na função serão apagados.

Para demonstrar, copie/cole a função abaixo diretamente no console do PowerShell.

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

Depois de colado, execute a função. Note que você não pode acessar a variável $var fora da função.

PS> Do-Thing
PS> $var

Mantendo Itens Privados (Desabilitando Herança)

Geralmente, se uma variável é definida em um escopo pai, essa variável será definida no escopo filho. Mas talvez você queira usar um nome de variável, porém esse nome de variável já foi definido em um dos escopos para ser executado em uma sessão. Nesse caso, você pode escolher um nome de variável diferente ou definir a variável em um escopo privado, tornando-a uma variável privada.

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

Você pode ver abaixo que estou definindo uma variável de escopo privado no escopo global e depois executando o script Test.ps1. Geralmente, quando você define uma variável em um escopo pai, essa variável estará disponível no escopo filho – não é o caso com uma variável de escopo privado.

No exemplo abaixo, você pode ver que o escopo filho do script criado pela execução do script Test.ps1 não conseguiu ver a variável $a de escopo privado definida no escopo pai.

Script scope cannot see the private-scoped variable

Ao contrário dos escopos globais ou da opção AllScope no cmdlet Set-Variable, as variáveis de escopo privado são uma excelente maneira de compartimentalizar itens.

Melhores Práticas de Escopo

Pensar que definir variáveis no escopo global ou usar a opção AllScope é a melhor abordagem é comum. Afinal, todas as variáveis estão disponíveis em todos os lugares. Não há necessidade de se preocupar com as complicações dos escopos. Embora isso forneça liberdade adicional para acessar o que está definido, pode rapidamente sair do controle e se tornar difícil de solucionar.

Em vez de tentar evitar o uso de escopos, siga estas dicas:

  1. Em vez de especificar escopos em funções, use parâmetros para passar as informações necessárias para a função.
  2. Permaneça dentro do escopo local o máximo possível.
  3. Em vez de definir variáveis globais a partir de um script, use o cmdlet Write-Output para exibir tudo e salve-o em uma variável quando necessário a partir do console.

O ponto principal aqui é abraçar os escopos e aprender a usá-los a seu favor, em vez de tentar evitá-los.

Leitura Adicional

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