O shell Bash oferece recursos poderosos para manipular nomes de arquivos e gerar strings de forma dinâmica. Entre eles, o globbing (ou expansão de caminhos) e a expansão de chaves são fundamentais para qualquer script que lide com múltiplos arquivos ou precise gerar combinações de strings. Dominar esses recursos aumenta a produtividade e evita armadilhas comuns.

Nesta aula, vamos explorar os curingas tradicionais, a sintaxe de chaves, e opções avançadas como extglob e nullglob. Ao final, você será capaz de escrever scripts mais concisos e robustos, lidando com padrões de arquivos de forma elegante.

Curingas (*, ?, [])

Os curingas são caracteres especiais que o shell expande para nomes de arquivos que correspondem a um padrão. Eles são essenciais para operações em lote, como copiar, mover ou listar arquivos que seguem um padrão específico.

Os três curingas básicos são:

  • * (asterisco): corresponde a qualquer sequência de caracteres (inclusive zero caracteres).
  • ? (ponto de interrogação): corresponde a exatamente um caractere.
  • [...] (colchetes): corresponde a um único caractere dentre os listados ou a um intervalo.

Vamos ver cada um em ação. Suponha que temos os seguintes arquivos em um diretório:

$ ls
dados1.txt dados2.txt dados10.txt relatorio.pdf script.sh backup.tar.gz

O curinga * é o mais comum. Por exemplo, para listar todos os arquivos com extensão .txt:

$ ls *.txt
dados1.txt dados2.txt dados10.txt

O curinga ? é útil para corresponder a um caractere exato. Por exemplo, para listar arquivos com um único caractere antes de .txt:

$ ls ?.txt
# Nenhum arquivo corresponde, pois temos dados1.txt, dados2.txt, dados10.txt

Na verdade, não há arquivo com um único caractere antes de .txt. Para encontrar arquivos com exatamente um dígito antes de .txt, podemos usar dados?.txt:

$ ls dados?.txt
dados1.txt dados2.txt

Os colchetes permitem definir um conjunto ou intervalo de caracteres. Por exemplo:

$ ls dados[12].txt
dados1.txt dados2.txt
$ ls dados[1-2].txt
dados1.txt dados2.txt
$ ls dados[!1].txt
dados2.txt dados10.txt  # [!1] nega, ou seja, qualquer caractere exceto 1

É importante notar que o globbing é realizado pelo shell antes da execução do comando. Se nenhum arquivo corresponder ao padrão, o padrão é passado literalmente ao comando (a menos que nullglob esteja ativo, como veremos adiante).

Outro ponto: o globbing não atravessa diretórios por padrão. Ou seja, *.txt não corresponde a arquivos em subdiretórios. Para isso, precisaríamos de ** (com globstar ativado) ou do comando find.

Expansão de chaves ({})

A expansão de chaves é um recurso que gera strings a partir de uma lista ou intervalo. Ela é semelhante ao globbing, mas não depende de nomes de arquivos existentes: ela simplesmente expande o padrão para todas as combinações possíveis.

A sintaxe básica é {item1,item2,...} ou {início..fim} para intervalos (numéricos ou alfabéticos). Exemplos:

$ echo {a,b,c}
a b c
$ echo {1..5}
1 2 3 4 5
$ echo {a..e}
a b c d e

A expansão de chaves é frequentemente usada para criar múltiplos arquivos ou diretórios de uma só vez:

$ touch arquivo{1..3}.txt
$ ls
arquivo1.txt arquivo2.txt arquivo3.txt

Também é possível combinar listas e intervalos, e até mesmo aninhar chaves:

$ echo {1..3}{a,b}
1a 1b 2a 2b 3a 3b
$ echo {a,b{1,2}}
a b1 b2

Uma diferença crucial: a expansão de chaves ocorre antes do globbing. Ou seja, o shell primeiro expande as chaves e depois aplica os curingas aos resultados. Por exemplo:

$ ls {dados1,dados2}.txt
dados1.txt dados2.txt

Aqui, a chave é expandida para dados1 e dados2, e depois o globbing é aplicado (mas como não há curingas, não há mudança).

Outro uso comum é em loops:

for i in {1..10}; do
    echo "Número $i"
done

Isso é muito mais legível do que usar seq ou listas manuais.

extglob

O Bash permite ativar padrões estendidos de globbing com a opção extglob. Quando ativada, você pode usar padrões mais complexos, semelhantes a expressões regulares, para corresponder a nomes de arquivos.

Para ativar, use shopt -s extglob. Para desativar, shopt -u extglob.

Os padrões estendidos incluem:

  • ?(padrão) – corresponde a zero ou uma ocorrência do padrão.
  • *(padrão) – zero ou mais ocorrências.
  • +(padrão) – uma ou mais ocorrências.
  • @(padrão1|padrão2) – exatamente um dos padrões.
  • !(padrão) – qualquer coisa exceto o padrão.

Exemplos práticos:

$ touch file1.txt file2.txt file.txt backup.zip
$ shopt -s extglob
$ ls file+([0-9]).txt   # arquivos com um ou mais dígitos
file1.txt file2.txt
$ ls !(*.txt)           # tudo que não termina em .txt
backup.zip
$ ls @(file|backup)*    # começa com file ou backup
file1.txt file2.txt file.txt backup.zip

Esses padrões são extremamente úteis para filtrar arquivos de forma precisa, evitando o uso de comandos externos como grep ou find apenas para seleção.

É importante lembrar que extglob afeta apenas o shell atual. Para torná-lo permanente, adicione shopt -s extglob ao seu ~/.bashrc.

nullglob

Por padrão, se um padrão glob não corresponder a nenhum arquivo, o Bash o deixa como está (passa o padrão literalmente ao comando). Isso pode causar comportamentos inesperados, como tentar copiar um arquivo chamado *.txt quando não há nenhum.

A opção nullglob muda esse comportamento: se nenhum arquivo corresponder, o padrão é removido (expandido para nada). Isso é útil em scripts para evitar erros.

Para ativar: shopt -s nullglob. Para desativar: shopt -u nullglob.

Exemplo:

$ shopt -s nullglob
$ ls *.txt 2>/dev/null
# Nenhum arquivo .txt, o comando ls é executado sem argumentos, listando o diretório atual
$ shopt -u nullglob
$ ls *.txt
ls: cannot access '*.txt': No such file or directory

No primeiro caso, com nullglob ativo, o padrão é removido e o ls lista o diretório atual. Sem nullglob, o comando recebe o literal *.txt e gera erro.

Em scripts, é comum usar nullglob em conjunto com arrays para armazenar listas de arquivos com segurança:

shopt -s nullglob
arquivos=(*.txt)
if (( ${#arquivos[@]} == 0 )); then
    echo "Nenhum arquivo .txt encontrado."
else
    echo "Encontrados ${#arquivos[@]} arquivos."
fi

Sem nullglob, o array conteria o literal *.txt se não houvesse correspondência, causando lógica incorreta.

Boas práticas e observações finais

Ao usar globbing e expansão de chaves, tenha em mente:

  • Use aspas em padrões que não devem ser expandidos, como em find . -name '*.txt'.
  • Combine nullglob com arrays para lidar com ausência de arquivos de forma robusta.
  • Prefira extglob para padrões complexos, mas documente seu uso, pois pode confundir outros desenvolvedores.
  • Cuidado com espaços em nomes de arquivos: sempre cite variáveis e use "$@" para preservar argumentos.

Esses recursos são fundamentais para escrever scripts eficientes e legíveis. Pratique combinando-os para resolver tarefas do dia a dia.

Exercícios

  1. Liste todos os arquivos que começam com dados e terminam com um número de 1 a 9, seguido de .txt.
  2. ✓ Resposta: ls dados[1-9].txt
  3. Gere uma lista de todos os meses do ano usando expansão de chaves (dica: use abreviações de 3 letras, jan, fev, ...).
  4. ✓ Resposta: echo {jan,fev,mar,abr,mai,jun,jul,ago,set,out,nov,dez}
  5. Usando extglob, liste apenas os arquivos que não terminam em .txt nem .zip.
  6. ✓ Resposta: ls !(*.txt|*.zip) (após shopt -s extglob)
  7. Escreva um comando que crie os diretórios projeto/src, projeto/docs e projeto/tests de uma só vez (use mkdir -p).
  8. ✓ Resposta: mkdir -p projeto/{src,docs,tests}
  9. Em um script, ative nullglob e use um array para contar quantos arquivos .log existem no diretório atual. Se não houver nenhum, exiba uma mensagem.
  10. ✓ Resposta:
    shopt -s nullglob
    logs=(*.log)
    if (( ${#logs[@]} == 0 )); then
        echo "Nenhum arquivo .log encontrado."
    else
        echo "Encontrados ${#logs[@]} arquivos .log"
    fi

Referências