O laço for é uma das estruturas de repetição mais utilizadas em scripts Bash, pois permite executar um conjunto de comandos para cada item de uma lista. Diferente de linguagens como C ou Python, o Bash oferece múltiplas sintaxes para o for, cada uma adequada a diferentes cenários, como iterar sobre argumentos, arquivos que correspondem a um padrão, ou até mesmo simular um contador numérico.

Nesta aula, vamos explorar as principais formas de usar o for: sobre listas explícitas, sobre arquivos via globbing, e a versão estilo C, que é útil para loops numéricos. Ao final, você terá uma compreensão sólida para aplicar em seus próprios scripts.

Sobre listas

No Bash, um laço for pode percorrer uma lista de palavras ou strings. A sintaxe básica é:

for variável in lista; do
    comandos
 done

A lista pode ser definida de várias formas: diretamente na linha de comando, através de uma variável, ou por expansão de comandos. Por exemplo, podemos iterar sobre números ou nomes:

#!/bin/bash
# Iterando sobre uma lista literal
for fruta in maçã banana laranja; do
    echo "Fruta: $fruta"
done

# Usando uma variável
lista="um dois três"
for item in $lista; do
    echo "Item: $item"
done

# Gerando lista com comando
for arquivo in $(ls); do
    echo "Arquivo: $arquivo"
done

É importante notar que, se a lista contiver espaços em branco dentro de um item, será necessário usar aspas ou escapar, pois o Bash divide a lista por espaços. Para listas mais complexas, pode-se usar arrays:

#!/bin/bash
# Array de frutas
frutas=("maçã" "banana" "laranja")
for fruta in "${frutas[@]}"; do
    echo "Fruta: $fruta"
done

Outra forma comum é iterar sobre uma sequência de números com o comando seq ou a expansão {início..fim}:

#!/bin/bash
# Usando expansão de chaves
for i in {1..5}; do
    echo "Número: $i"
done

# Usando comando seq
for i in $(seq 1 2 10); do
    echo "Número (incremento 2): $i"
done

O laço for também pode percorrer os argumentos de linha de comando usando "$@", o que é útil para scripts que aceitam parâmetros:

#!/bin/bash
for arg in "$@"; do
    echo "Argumento: $arg"
done

Sobre arquivos (globbing)

O globbing é um recurso do shell que expande padrões de arquivos, como *.txt, em uma lista de nomes de arquivos correspondentes. Isso é extremamente útil com o laço for para processar vários arquivos de uma só vez.

Por exemplo, para renomear todos os arquivos .txt para .bak, podemos fazer:

#!/bin/bash
for arquivo in *.txt; do
    mv "$arquivo" "${arquivo%.txt}.bak"
done

Atenção: se não houver nenhum arquivo correspondente ao padrão, o glob permanece literal (a menos que a opção nullglob esteja ativada). Para evitar erros, é comum verificar se o arquivo existe:

#!/bin/bash
for arquivo in *.txt; do
    if [ -e "$arquivo" ]; then
        echo "Processando $arquivo"
    else
        echo "Nenhum arquivo .txt encontrado"
    fi
done

Também é possível usar padrões mais complexos, como *.log, data?.txt, ou combinar múltiplos padrões:

#!/bin/bash
# Processar arquivos .jpg e .png
for imagem in *.jpg *.png; do
    echo "Imagem: $imagem"
done

O globbing também funciona com caminhos, como ./subdir/*.conf, e pode ser usado para iterar sobre todos os arquivos de um diretório, incluindo subdiretórios, usando o globstar (se ativado com shopt -s globstar):

#!/bin/bash
shopt -s globstar
for arquivo in **/*.sh; do
    echo "Script: $arquivo"
done

É fundamental citar as variáveis com aspas duplas para evitar que o shell interprete caracteres especiais contidos nos nomes dos arquivos.

for C-style

O Bash também suporta uma sintaxe de for inspirada na linguagem C, que é útil para loops numéricos com controle de incremento. A sintaxe é:

for (( inicialização; condição; incremento )); do
    comandos
done

Por exemplo, para imprimir números de 1 a 10:

#!/bin/bash
for (( i=1; i<=10; i++ )); do
    echo "Número: $i"
done

Essa forma permite expressões aritméticas completas, como decremento, incremento de mais de um, ou condições complexas:

#!/bin/bash
# Decrecente de 10 a 1
for (( i=10; i>0; i-- )); do
    echo "Contagem regressiva: $i"
done

# Incremento de 2
for (( i=0; i<=20; i+=2 )); do
    echo "Par: $i"
done

# Condição composta
for (( i=1, j=10; i<=j; i++, j-- )); do
    echo "i=$i, j=$j"
done

Esse estilo é particularmente útil quando você precisa de um controle fino sobre o índice, como em operações com arrays por índice:

#!/bin/bash
frutas=("maçã" "banana" "laranja")
for (( i=0; i<${#frutas[@]}; i++ )); do
    echo "Fruta $((i+1)): ${frutas[$i]}"
done

Perceba que as variáveis de controle são inteiras, e as expressões são avaliadas no contexto aritmético do Bash.

Exemplos

Vamos consolidar com alguns exemplos práticos que combinam as diferentes formas do laço for:

Exemplo 1: Backup de arquivos com data

#!/bin/bash
# Cria backup de todos os .conf em um diretório com timestamp
backup_dir="backup_$(date +%Y%m%d_%H%M%S)"
mkdir -p "$backup_dir"
for arquivo in *.conf; do
    if [ -e "$arquivo" ]; then
        cp "$arquivo" "$backup_dir/"
        echo "Copiado $arquivo"
    fi
done

Exemplo 2: Somatório com C-style

#!/bin/bash
# Calcula a soma de 1 a 100
soma=0
for (( i=1; i<=100; i++ )); do
    soma=$((soma + i))
done
echo "Soma: $soma"

Exemplo 3: Processar argumentos com validação

#!/bin/bash
# Exibe os argumentos que são arquivos regulares
for arg in "$@"; do
    if [ -f "$arg" ]; then
        echo "$arg é um arquivo regular."
    elif [ -d "$arg" ]; then
        echo "$arg é um diretório."
    else
        echo "$arg não é um arquivo ou diretório."
    fi
done

Exemplo 4: Iteração sobre linhas de um arquivo

Embora o while read seja mais adequado para ler linhas, o for pode ser usado com o comando cat, mas cuidado com espaços:

#!/bin/bash
# Lê palavras de um arquivo (quebra por espaços)
for palavra in $(cat arquivo.txt); do
    echo "Palavra: $palavra"
done

Se o arquivo tiver linhas com espaços, prefira while.

Exemplo 5: Geração de relatório com múltiplos arquivos

#!/bin/bash
# Conta linhas de todos os scripts .sh no diretório atual
total=0
for script in *.sh; do
    if [ -e "$script" ]; then
        linhas=$(wc -l < "$script")
        echo "$script: $linhas linhas"
        total=$((total + linhas))
    fi
done
echo "Total de linhas: $total"

Esses exemplos mostram a versatilidade do laço for no Bash. Lembre-se de sempre testar seus scripts com entradas variadas para garantir robustez.

Boas práticas e observações finais

  • Sempre use aspas duplas em variáveis que podem conter espaços ou caracteres especiais, especialmente em nomes de arquivos.
  • Prefira for com globbing para arquivos, mas verifique se o padrão não é literal (use nullglob se necessário).
  • Para loops numéricos, o estilo C-style é mais legível e eficiente do que usar seq.
  • Evite usar for para ler linhas de arquivos; utilize while read para preservar espaços e caracteres especiais.
  • Teste seus scripts com bash -x para depurar o comportamento do laço.

Exercícios

  1. Escreva um script que use um laço for para imprimir os números de 1 a 20, mas apenas os pares.

    ✓ Resposta:
    #!/bin/bash
    for (( i=1; i<=20; i++ )); do
        if (( i % 2 == 0 )); then
            echo $i
        fi
    done
  2. Crie um script que renomeie todos os arquivos com extensão .txt no diretório atual para .bak, usando globbing.

    ✓ Resposta:
    #!/bin/bash
    for arquivo in *.txt; do
        if [ -e "$arquivo" ]; then
            mv "$arquivo" "${arquivo%.txt}.bak"
        fi
    done
  3. Escreva um script que some todos os argumentos passados na linha de comando (suponha que sejam números).

    ✓ Resposta:
    #!/bin/bash
    soma=0
    for num in "$@"; do
        soma=$((soma + num))
    done
    echo "Soma: $soma"
  4. Utilizando o estilo C-style, crie um script que imprima uma contagem regressiva de 10 a 1, e depois a mensagem "Lançar!".

    ✓ Resposta:
    #!/bin/bash
    for (( i=10; i>0; i-- )); do
        echo $i
    done
    echo "Lançar!"
  5. Escreva um script que liste todos os arquivos .log no diretório atual e, para cada um, exiba seu tamanho em bytes usando o comando stat.

    ✓ Resposta:
    #!/bin/bash
    for arquivo in *.log; do
        if [ -e "$arquivo" ]; then
            tamanho=$(stat -c %s "$arquivo")
            echo "$arquivo: $tamanho bytes"
        fi
    done

Referências