Loop sobre arquivos com glob e find
1. Introdução ao globbing e ao comando find
Globbing é o mecanismo de expansão de padrões de nomes de arquivos que o shell realiza automaticamente. Os padrões mais comuns incluem * (qualquer sequência de caracteres), ? (um caractere qualquer) e [] (conjunto de caracteres). Quando você escreve *.txt no terminal, o Bash expande isso para uma lista de todos os arquivos terminados em .txt no diretório atual.
O comando find, por outro lado, é uma ferramenta poderosa que percorre a árvore de diretórios aplicando filtros complexos. Enquanto o globbing é simples e direto para casos básicos, o find oferece flexibilidade para buscas recursivas, por tipo de arquivo, tamanho, data de modificação e muito mais.
A principal diferença prática: o globbing expande padrões no próprio shell antes da execução do comando, enquanto o find gera uma lista de arquivos dinamicamente durante a execução. Isso tem implicações importantes para tratamento de nomes com espaços e caracteres especiais.
2. Loop com glob básico
A forma mais simples de iterar sobre arquivos com glob é usando o loop for:
for file in *.txt; do
echo "Processando: $file"
done
Um problema comum: se não houver arquivos .txt, o glob não expande e o loop tenta processar o literal *.txt. Para evitar isso, ative a opção nullglob:
shopt -s nullglob
for file in *.txt; do
echo "Processando: $file"
done
shopt -u nullglob
Com Bash 4 ou superior, você pode usar glob recursivo com ** para percorrer subdiretórios:
shopt -s globstar
for file in **/*.txt; do
echo "Arquivo: $file"
done
3. Loop com glob avançado
Você pode combinar múltiplos padrões usando chaves:
for file in *.{jpg,png,gif}; do
echo "Imagem: $file"
done
Para padrões mais complexos, ative o extglob:
shopt -s extglob
# Arquivos que começam com "backup" ou terminam com ".tmp"
for file in +(backup*) +(*.tmp); do
echo "$file"
done
Padrões com extglob:
- ?(pattern) — zero ou uma ocorrência
- *(pattern) — zero ou mais ocorrências
- +(pattern) — uma ou mais ocorrências
- @(pattern) — exatamente uma ocorrência
- !(pattern) — qualquer coisa exceto o padrão
Para filtrar apenas diretórios com glob:
for dir in */; do
echo "Diretório: $dir"
done
E para arquivos ocultos (que começam com ponto):
for file in .*; do
echo "Oculto: $file"
done
4. Loop com find e while read
A abordagem mais segura para processar arquivos com find usa -print0 e while read:
find . -type f -name "*.log" -print0 | while IFS= read -r -d '' file; do
echo "Processando: $file"
wc -l "$file"
done
Por que evitar for file in $(find ...)? Considere um arquivo chamado meu arquivo.txt — o comando find retornaria ./meu arquivo.txt, e o loop for quebraria isso em duas iterações: ./meu e arquivo.txt. O mesmo ocorre com quebras de linha nos nomes.
O -print0 usa o caractere nulo como delimitador, que é o único caractere que não pode aparecer em nomes de arquivos no Linux. O read -d '' lê até encontrar esse delimitador, preservando nomes completos.
Exemplo com filtros do find:
find /var/log -type f -name "*.log" -size +1M -print0 | while IFS= read -r -d '' file; do
echo "Arquivo grande: $file ($(du -h "$file" | cut -f1))"
done
5. Usando find com -exec e xargs
O find pode executar comandos diretamente com -exec:
# Executa uma vez para cada arquivo (lento, mas seguro)
find . -name "*.tmp" -exec rm {} \;
# Executa uma vez com todos os arquivos como argumentos (mais rápido)
find . -name "*.tmp" -exec rm {} +
A diferença: \; executa o comando para cada arquivo individualmente, enquanto + agrupa vários arquivos em uma única chamada de comando, similar ao xargs.
Combinando find e xargs de forma segura:
find . -type f -name "*.bak" -print0 | xargs -0 rm -f
A opção -0 do xargs corresponde ao -print0 do find, garantindo que nomes com espaços sejam tratados corretamente.
Performance: para operações simples como remoção ou cópia, -exec {} + e xargs -0 têm desempenho similar. Para comandos que precisam de processamento individual, while read ou -exec {} \; são mais apropriados.
6. Lidando com nomes de arquivos problemáticos
Nomes de arquivos podem conter espaços, quebras de linha, tabs e outros caracteres especiais. Veja um exemplo prático de como lidar com isso:
# Criando arquivos com nomes problemáticos para teste
touch "arquivo com espacos.txt"
touch "arquivo
com
quebras.txt"
touch "arquivo'com'aspas.txt"
# Processamento seguro com find e while read
find . -type f -name "*.txt" -print0 | while IFS= read -r -d '' file; do
# Usar aspas duplas em torno de $file é essencial
cp "$file" "/backup/$(basename "$file")"
echo "Copiado: $file"
done
Regras de ouro:
1. Sempre use aspas duplas ao expandir variáveis com nomes de arquivos
2. Use -print0 com find e -0 com xargs
3. Defina IFS= para evitar que o read divida por espaços ou tabs
4. Use -d '' no read para usar o delimitador nulo
7. Exemplos práticos e boas práticas
Renomear arquivos em lote com glob:
# Adicionar prefixo "backup_" a todos os arquivos .conf
shopt -s nullglob
for file in *.conf; do
mv "$file" "backup_$file"
done
Buscar e processar arquivos recursivamente com find:
# Encontrar todos os arquivos .py com mais de 100 linhas e contar linhas
find . -type f -name "*.py" -size +100c -print0 | while IFS= read -r -d '' file; do
lines=$(wc -l < "$file")
echo "$file: $lines linhas"
done
Dicas de performance:
- Para diretórios com poucos arquivos (centenas), o globbing é mais rápido
- Para diretórios com milhares de arquivos, o
findé mais eficiente - Evite
for file in $(find ...)a todo custo — além de inseguro, é lento - Use
find -exec {} +ouxargs -0para operações em lote - Para scripts portáteis, prefira
findem vez deglobstar(que é específico do Bash)
Exemplo completo: backup seletivo
#!/bin/bash
shopt -s nullglob
backup_dir="/tmp/backup_$(date +%Y%m%d)"
mkdir -p "$backup_dir"
# Usando glob para arquivos locais
for file in *.doc *.pdf; do
cp "$file" "$backup_dir/"
done
# Usando find para busca recursiva de logs recentes
find /var/log -type f -name "*.log" -mtime -7 -print0 | \
while IFS= read -r -d '' file; do
cp "$file" "$backup_dir/"
done
echo "Backup concluído em $backup_dir"
A escolha entre glob e find depende do contexto: para operações simples em diretórios rasos, o glob é suficiente. Para buscas complexas ou recursivas com segurança contra nomes problemáticos, o find com -print0 é a abordagem correta.
Referências
-
Bash Reference Manual: Pattern Matching — Documentação oficial da GNU sobre padrões de globbing no Bash, incluindo extglob e globstar.
-
GNU Findutils Manual — Manual completo do comando find, com todas as opções de filtro, -exec e -print0.
-
Greg's Wiki: Bash Pitfalls — Lista de armadilhas comuns em Bash, incluindo erros ao iterar sobre arquivos com for e find.
-
ShellCheck: SC2044 — Explicação detalhada sobre por que usar
while readem vez deforcom find, com exemplos práticos. -
Stack Overflow: Why is looping over find output bad practice? — Discussão técnica sobre os problemas de segurança e portabilidade ao usar
for file in $(find ...).