Laços de repetição são fundamentais em qualquer linguagem de programação, permitindo executar um bloco de código múltiplas vezes. Em PowerShell, temos diversas construções para iteração, sendo for e foreach duas das mais comuns. Nesta aula, vamos explorar cada uma delas em detalhes, entender suas particularidades e aprender quando usar cada uma.

O laço for é ideal quando você precisa controlar explicitamente o número de iterações, geralmente com uma variável contadora. Já o foreach é mais intuitivo para percorrer coleções de objetos, como arrays ou listas, sem se preocupar com índices. Além disso, veremos a diferença entre o statement foreach e o cmdlet ForEach-Object, que são frequentemente confundidos.

for

O laço for executa um bloco de código enquanto uma condição for verdadeira. Sua sintaxe é composta por três partes: inicialização, condição e incremento/decremento. A inicialização ocorre uma vez no início; a condição é avaliada antes de cada iteração; e o incremento é executado ao final de cada iteração.

Sintaxe básica:

for (<inicialização>; <condição>; <incremento>)
{
    <bloco de comando>
}

Exemplo: exibir números de 1 a 5.

for ($i = 1; $i -le 5; $i++)
{
    Write-Host "Número: $i"
}

Você pode usar o for para percorrer arrays usando índices:

$frutas = @("maçã", "banana", "laranja")
for ($i = 0; $i -lt $frutas.Length; $i++)
{
    Write-Host "Fruta $i: $($frutas[$i])"
}

Também é possível criar loops infinitos (embora não recomendado) omitindo a condição:

for (;;) { Write-Host "Loop infinito" }

foreach (statement)

O foreach statement é uma construção de linguagem que itera sobre cada elemento de uma coleção (como um array ou uma lista). Sua sintaxe é mais simples que o for, pois não requer variável de índice.

Sintaxe:

foreach (<variável> in <coleção>)
{
    <bloco de comando>
}

Exemplo com array:

$frutas = @("maçã", "banana", "laranja")
foreach ($fruta in $frutas)
{
    Write-Host "Fruta: $fruta"
}

O foreach pode ser usado com qualquer objeto que implemente a interface IEnumerable, como resultados de cmdlets:

foreach ($processo in Get-Process)
{
    Write-Host "Processo: $($processo.Name)"
}

Importante: o foreach statement carrega toda a coleção na memória antes de iterar, o que pode ser problemático com coleções muito grandes. Nesses casos, considere usar o pipeline com ForEach-Object.

Diferença para ForEach-Object

O cmdlet ForEach-Object (aliases: foreach, %) é usado no pipeline para processar objetos um de cada vez, sem carregar toda a coleção na memória. Ele é mais eficiente para grandes volumes de dados e permite encadeamento com outros cmdlets.

Sintaxe:

<coleção> | ForEach-Object { <bloco de comando> }

Dentro do bloco, o objeto atual é representado por $_ ou $PSItem.

Exemplo:

1..5 | ForEach-Object { Write-Host "Número: $_" }

Principais diferenças:

  • Memória: foreach statement carrega toda a coleção na memória; ForEach-Object processa um item por vez.
  • Pipeline: ForEach-Object se integra perfeitamente ao pipeline; foreach statement não.
  • Desempenho: foreach é geralmente mais rápido para coleções pequenas e médias; ForEach-Object tem overhead de pipeline.
  • Uso: foreach é mais legível para scripts simples; ForEach-Object é preferido em expressões no pipeline.

Exemplo comparativo: somar números de 1 a 1000.

Com foreach:

$soma = 0
foreach ($n in 1..1000) { $soma += $n }
Write-Host "Soma: $soma"

Com ForEach-Object:

$soma = 0
1..1000 | ForEach-Object { $soma += $_ }
Write-Host "Soma: $soma"

Ambos produzem o mesmo resultado, mas o foreach tende a ser mais rápido.

Exemplos

Vamos ver alguns exemplos práticos que combinam os conceitos.

Exemplo 1: Percorrer um array de números e exibir apenas os pares.

$numeros = 1..10
foreach ($num in $numeros)
{
    if ($num % 2 -eq 0)
    {
        Write-Host "$num é par"
    }
}

Exemplo 2: Usar for para criar uma tabuada de multiplicação.

$numero = 7
for ($i = 1; $i -le 10; $i++)
{
    Write-Host "$numero x $i = $($numero * $i)"
}

Exemplo 3: Processar arquivos em um diretório com ForEach-Object.

Get-ChildItem -Path C:\Temp -File | ForEach-Object {
    Write-Host "Arquivo: $($_.Name), Tamanho: $($_.Length) bytes"
}

Exemplo 4: Uso de break e continue dentro de foreach.

$numeros = 1..10
foreach ($n in $numeros)
{
    if ($n -eq 5) { continue }  # pula o 5
    if ($n -eq 8) { break }     # para no 8
    Write-Host $n
}
# Saída: 1 2 3 4 6 7

Exemplo 5: Aninhamento de loops for para matriz bidimensional.

for ($i = 0; $i -lt 3; $i++)
{
    for ($j = 0; $j -lt 3; $j++)
    {
        Write-Host "Posição [$i][$j]"
    }
}

Boas Práticas

  • Prefira foreach statement quando a coleção for pequena e você precisar de máxima legibilidade.
  • Use ForEach-Object quando estiver trabalhando com pipelines ou coleções muito grandes.
  • Evite modificar a coleção dentro do loop (ex.: adicionar ou remover itens) pois pode causar comportamentos inesperados.
  • Utilize break para sair mais cedo e continue para pular iterações quando necessário.

Referências

Exercícios

  1. Escreva um script que use o laço for para exibir os números ímpares de 1 a 20.

    ✓ Resposta:
    for ($i = 1; $i -le 20; $i += 2)
    {
        Write-Host $i
    }
  2. Dado o array $cores = @("vermelho", "azul", "verde", "amarelo"), use o foreach statement para exibir cada cor em letras maiúsculas.

    ✓ Resposta:
    $cores = @("vermelho", "azul", "verde", "amarelo")
    foreach ($cor in $cores)
    {
        Write-Host $cor.ToUpper()
    }
  3. Use ForEach-Object para exibir o nome de cada processo em execução que tenha mais de 10 MB de memória (WorkingSet). Dica: Get-Process retorna objetos com a propriedade WorkingSet (em bytes). Converta para MB.

    ✓ Resposta:
    Get-Process | ForEach-Object {
        if ($_.WorkingSet -gt 10MB)
        {
            Write-Host $_.Name
        }
    }
  4. Crie um script que, usando o laço for, calcule a soma dos números de 1 a 100 e exiba o resultado.

    ✓ Resposta:
    $soma = 0
    for ($i = 1; $i -le 100; $i++)
    {
        $soma += $i
    }
    Write-Host "Soma: $soma"
  5. Escreva um script que use foreach para percorrer os arquivos em C:\Windows\System32 (apenas arquivos, não pastas) e exiba o nome e o tamanho em KB daqueles que têm tamanho maior que 50 KB.

    ✓ Resposta:
    $arquivos = Get-ChildItem -Path C:\Windows\System32 -File
    foreach ($arquivo in $arquivos)
    {
        $tamanhoKB = $arquivo.Length / 1KB
        if ($tamanhoKB -gt 50)
        {
            Write-Host "$($arquivo.Name) - $([math]::Round($tamanhoKB, 2)) KB"
        }
    }