ForEach-Object
Esta aula aborda o cmdlet ForEach-Object, explorando sua sintaxe, o uso da variável $_ e os blocos Begin, Process e End para controle de fluxo no pipeline. Compara também o ForEach-Object com a instrução foreach tradicional.
O cmdlet ForEach-Object é uma ferramenta essencial no PowerShell para processar cada item que passa pelo pipeline. Ele permite executar um bloco de script para cada objeto recebido, sendo amplamente utilizado em operações de filtragem, transformação e agregação de dados. Diferente da instrução foreach que carrega toda a coleção na memória, o ForEach-Object processa os itens um a um conforme eles fluem pelo pipeline, o que é mais eficiente para grandes conjuntos de dados.
Nesta aula, vamos explorar em detalhes como usar o ForEach-Object, incluindo a variável $_ que representa o objeto atual, e os blocos especiais Begin, Process e End que oferecem controle granular sobre o fluxo de processamento. Ao final, compararemos o ForEach-Object com a instrução foreach tradicional para que você possa escolher a abordagem mais adequada para cada situação.
Iteração no pipeline
O pipeline do PowerShell permite encadear comandos, passando objetos de um comando para o próximo. O ForEach-Object é um cmdlet que atua como um 'loop' dentro do pipeline: para cada objeto que chega, ele executa um bloco de script. A sintaxe básica é:
<comando> | ForEach-Object { <bloco de script> }
O bloco de script entre chaves é executado uma vez para cada objeto no pipeline. Dentro do bloco, usamos a variável automática $_ (ou $PSItem) para referenciar o objeto atual. Por exemplo, para exibir o nome de cada processo:
Get-Process | ForEach-Object { $_.Name }
Isso produzirá uma lista com os nomes de todos os processos. Note que o ForEach-Object é um cmdlet, então ele pode ser usado em qualquer lugar onde um cmdlet é esperado, inclusive com parâmetros nomeados.
Uma variação comum é usar o parâmetro -MemberName para acessar uma propriedade diretamente, sem precisar de um bloco de script:
Get-Process | ForEach-Object -MemberName Name
Isso é mais conciso e tem desempenho ligeiramente melhor porque evita a avaliação do script block.
$_
A variável $_ (dólar sublinhado) é uma variável automática do PowerShell que representa o objeto atual no pipeline. Ela é frequentemente chamada de 'this' ou 'current object'. Dentro de um bloco de script do ForEach-Object, $_ contém o objeto que está sendo processado naquele momento. Por exemplo:
1..5 | ForEach-Object { $_ * 2 }
Saída: 2, 4, 6, 8, 10. Aqui, $_ assume os valores 1, 2, 3, 4 e 5 sequencialmente.
É importante notar que $_ também está disponível em outros constructos, como no cmdlet Where-Object e em blocos de script de propriedades calculadas. Seu escopo é limitado ao bloco de script atual. Em versões recentes do PowerShell, você também pode usar $PSItem como alias de $_, o que pode tornar o código mais legível.
Exemplo prático: calcular o quadrado de números pares em uma lista:
1..10 | Where-Object { $_ % 2 -eq 0 } | ForEach-Object { $_ * $_ }
Saída: 4, 16, 36, 64, 100.
Blocos Begin/Process/End
O ForEach-Object aceita até três blocos de script nomeados: Begin, Process e End. Esses blocos permitem executar ações antes de processar o primeiro item, para cada item, e depois de processar o último item, respectivamente. A sintaxe é:
<comando> | ForEach-Object -Begin { <bloco> } -Process { <bloco> } -End { <bloco> }
O bloco Begin é executado uma vez, antes de qualquer objeto ser processado. É útil para inicializar variáveis, abrir arquivos ou escrever cabeçalhos. O bloco Process é executado para cada objeto no pipeline (sem ele, nada é processado). O bloco End é executado uma vez após todos os objetos terem sido processados, ideal para finalização, como fechar arquivos ou exibir totais.
Exemplo: calcular a soma de uma lista de números, exibindo o total no final:
1..5 | ForEach-Object -Begin { $sum = 0 } -Process { $sum += $_ } -End { Write-Host "Soma total: $sum" }
Saída: Soma total: 15. Note que o bloco Begin inicializa a variável $sum, o Process acumula cada valor, e o End exibe o resultado.
Esses blocos são especialmente úteis quando você precisa de contexto entre iterações, como calcular médias, encontrar valores máximos/mínimos ou gerar relatórios.
vs foreach statement
A instrução foreach (também conhecida como ForEach-Object no pipeline) é diferente do cmdlet ForEach-Object. A instrução foreach é uma construção de linguagem que itera sobre uma coleção já carregada na memória. Sua sintaxe é:
foreach ($item in $colecao) { <bloco> }
Ela é mais rápida para coleções pequenas e já disponíveis, pois não envolve overhead de pipeline. No entanto, ela exige que toda a coleção seja armazenada em memória antes da iteração, o que pode ser problemático para conjuntos grandes.
O cmdlet ForEach-Object, por outro lado, processa itens à medida que eles chegam pelo pipeline, sem precisar armazenar toda a coleção. Isso é mais eficiente em termos de memória e permite combinar com outros cmdlets em um pipeline longo. Além disso, ForEach-Object suporta os blocos Begin/Process/End, enquanto a instrução foreach não.
Exemplo de comparação: somar os números de 1 a 1 milhão.
Com instrução foreach:
$numeros = 1..1000000
$soma = 0
foreach ($n in $numeros) { $soma += $n }
$soma
Com ForEach-Object:
1..1000000 | ForEach-Object -Begin { $soma = 0 } -Process { $soma += $_ } -End { $soma }
Ambos produzem o mesmo resultado, mas o ForEach-Object não precisa armazenar o array $numeros explicitamente (embora o operador .. também gere o array na memória). Em geral, use a instrução foreach quando você já tem a coleção em uma variável e o tamanho é gerenciável; use ForEach-Object quando estiver no meio de um pipeline ou quando a coleção for grande demais para caber na memória de uma vez.
Boas práticas
Ao trabalhar com ForEach-Object, lembre-se de que o bloco Process é o coração do cmdlet. Se você precisar apenas de uma propriedade, prefira o parâmetro -MemberName para melhor desempenho. Evite usar ForEach-Object quando a instrução foreach for mais clara e eficiente. Além disso, cuidado com o uso excessivo de pipeline para operações simples: loops explícitos podem ser mais rápidos. Sempre considere a legibilidade e a manutenção do código.
Referências
- Documentação oficial do ForEach-Object
- Sobre variáveis automáticas (incluindo $_)
- Sobre a instrução foreach
- Sobre pipelines
- Tudo sobre foreach (artigo aprofundado)
- Sobre blocos de script
Exercícios
-
Escreva um comando que use
ForEach-Objectpara exibir o nome e o tamanho (em KB) de cada arquivo no diretório atual. Use o cmdletGet-ChildItempara obter os arquivos.✓ Resposta:Get-ChildItem -File | ForEach-Object { Write-Host "$($_.Name): $($_.Length/1KB) KB" } -
Usando os blocos
Begin/Process/End, crie um comando que receba uma lista de números via pipeline e exiba a soma, a média e a quantidade de números. Teste com1..10.✓ Resposta:1..10 | ForEach-Object -Begin { $sum = 0; $count = 0 } -Process { $sum += $_; $count++ } -End { $avg = $sum / $count; Write-Host "Soma: $sum, Média: $avg, Quantidade: $count" } -
Converta o seguinte código que usa a instrução
foreachpara usar o cmdletForEach-Objectcom pipeline:$services = Get-Service foreach ($svc in $services) { if ($svc.Status -eq 'Running') { $svc.Name } }✓ Resposta:Get-Service | ForEach-Object { if ($_.Status -eq 'Running') { $_.Name } } -
Explique a diferença entre
Get-Process | ForEach-Object { $_.Name }eGet-Process | ForEach-Object -MemberName Name. Qual é mais eficiente e por quê?✓ Resposta:A segunda forma (
-MemberName Name) é mais eficiente porque não precisa compilar e executar um bloco de script para cada objeto. Ela acessa diretamente a propriedade usando mecanismos internos, resultando em menor overhead. Para coleções grandes, a diferença de desempenho é significativa. -
Crie um comando que use
ForEach-Objectpara ler o conteúdo de um arquivo de texto (cada linha como um objeto) e exibir apenas as linhas que contêm a palavra 'ERROR', usandoWhere-Objectantes deForEach-Object.✓ Resposta:Get-Content arquivo.log | Where-Object { $_ -match 'ERROR' } | ForEach-Object { Write-Host $_ }