Na maioria das vezes, o relatório de erros no PowerShell é muito útil. Vejo erros, vejo a origem, mas notei que quaisquer erros nos pipelines ForEach-Object perdem sua linha de origem e a pilha de erros simplesmente aponta para a linha de código com a extensão ForEach-Object
.
Exemplo de localização de erro incorreta
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
[cultureinfo]::CurrentUICulture = 'en-US'
function Test-Something($val)
{
# var is not defined here
Write-Host($undefined_variable)
}
@("a","b") | ForEach-Object{
$entry = $_
Test-Something $entry
}
Resulta em
ForEach-Object : The variable '$undefined_variable' cannot be retrieved because it has not been set.
In D:\dummy.ps1:12 Line:14
+ @("a","b") | ForEach-Object{
+ ~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (undefined_variable:String) [ForEach-Object], RuntimeException
+ FullyQualifiedErrorId : VariableIsUndefined,Microsoft.PowerShell.Commands.ForEachObjectCommand
A linha 12
aponta para @("a","b") | ForEach-Object{
, que obviamente não é o local do erro.
Exemplo muito mais utilizável com foreach
Agora estou usando um foreach
(apenas as últimas 4 linhas de código foram alteradas)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
[cultureinfo]::CurrentUICulture = 'en-US'
function Test-Something($val)
{
# var is not defined here
Write-Host($undefined_variable)
}
$data = @("a","b")
foreach($entry in $data)
{
Test-Something $entry
}
O erro agora é muito mais utilizável, na verdade aponta para a linha de erro 9
paraWrite-Host($undefined_variable)
The variable '$undefined_variable' cannot be retrieved because it has not been set.
In D:\dummy.ps1:9 Line:16
+ Write-Host($undefined_variable)
+ ~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (undefined_variable:String) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : VariableIsUndefined
Isso está relacionado à natureza da tubulação dos operadores do gasoduto ou estou entendendo algo errado. O primeiro cenário torna o rastreamento de erros muito difícil quando há mais funções entre eles. Na maioria dos casos, posso mudar, foreach
mas adoraria entender o que realmente está acontecendo.
Isso pode ser uma simplificação excessiva, e alguém com conhecimento formal mais profundo pode ter uma resposta técnica mais precisa, mas vale a pena notar que
for
eforeach
são instruções internas do PowerShell (chamadas de "comandos de linguagem" nos links da documentação) que contêm uma lista de instruções aninhadas:Eles fazem parte da sintaxe do PowerShell e o mecanismo sabe qual instrução da lista está sendo executada, enquanto
ForEach-Object
é "apenas" um cmdlet como, por exemplo,Invoke-Command
ouGet-ChldItem
.Como resultado, seu
foreach-object
exemplo é equivalente a este:ForEach-Object
Os parâmetros posicionais de permitem que você invoque o cmdlet com sintaxe semelhante afor
/foreach
, mas a mecânica subjacente é diferente e, no que diz respeito ao mecanismo do PowerShell, a linha de código de nível superior que relata o erro é a linha 7, não a 4 .Ele não remonta à linha de código-fonte que define o bloco de script - no seu caso provavelmente poderia , mas acho que seria difícil em um caso mais planejado, onde o bloco de script está sendo gerado a partir de código dinâmico, por exemplo
Nesse caso, qual número de linha o erro deve relatar?
Atualizar
De acordo com o comentário de @ zett42 sobre rastreamentos de pilha, o PowerShell contém informações sobre qual linha lança a exceção na
$_.ScriptStackTrace
propriedade da exceção - veja, por exemplo:Executar isso dá:
Portanto, a resposta simples é que parece que o PowerShell apenas relata a linha de nível superior que a exceção atingiu. No caso de
for
eforeach
essa é a linha que contém as instruções aninhadas, e forforeach-object
é a linhaforeach-object
em que aparece.E para responder à minha própria pergunta retórica:
Para um scriptblock dinâmico, é o número da linha interna relativo à origem do scriptblock (e não o número da linha do script principal) - veja:
Para uma versão mais rápida e informativa do ForEach-Object, use
.{process {
<code>}}
.Esta versão é mais otimizada e executada em menos tempo, mas é indiscutivelmente menos legível e carece de algumas funcionalidades do ForEach-Object. No entanto, como a funcionalidade extra raramente é usada, ela é uma substituição individual na maioria dos casos. Nos casos em que você deseja usar
-Begin
ou-End
, remova o comentário conforme necessário nas linhasbegin {}
ouend {}
e adicione o código desejado.Este código:
Produz esta saída:
Explicação:
(Pule para o número 7 para o verão de 2 a 6)
begin {}
. A seção é executada antes de qualquer entrada do pipeline ser processada, executada uma vez para cada item no pipeline e, posteriormente, a seção é executada.process {}
end {}
begin {}
process {}
end {}
begin {}
é que você pode definir funções nele e também variáveis que podem ser usadas para acumular informações relacionadas a itens no pipeline (como uma contagem ou soma).end {}
é que você pode criar a saída do pipeline desejada e enviá-la logo antes da saída do bloco de script (isso pode incluir informações como contagem ou soma).process {}
seção que é executada uma vez por cada item do pipeline, com cada item atribuído à variável automática$_
e com o aumento de desempenho adicional de não usar um cmdlet para obter o trabalho feito.Desempenho:
Santiago, nos comentários, destacou que a operadora Call
&
é otimizada internamente, enquanto a operadora Dot Sourcing.
não. Acredito que ele descobriu isso observando o código-fonte do PowerShell. Pensando nisso, o uso da operadora Call deveria ter melhor desempenho em relação à operadora Dot Sourcing, então executei um teste de desempenho.O problema de executar testes em um computador é que os processos externos podem causar problemas de desempenho que interferem no tempo de execução do código do PowerShell. Para distribuir esses problemas uniformemente em cada método, os resultados do teste para o operador Call, Dot Sourcing, loop Foreach e cmdlet Foreach-Object envolvem a execução de cada método em turnos para somar um milhão de números aleatórios (0-99), com um total de 100 execuções por método. O tempo total foi calculado como a soma de todas as execuções de cada método.
Como você pode ver nas tabelas a seguir, o operador Call
&
executou a tarefa no menor tempo possível.PowerShell 5.1.19041.3803
PowerShell 7.3.9
Código de teste do operador de chamada
&
:.
Código de teste do operador de fornecimento de pontos :Código de teste do loop Foreach:
Código de teste do cmdlet Foreach-Object:
Código para executar o teste:
Imprimindo os resultados do teste: