Arquivos temporários são uma necessidade comum em scripts que processam dados intermediários, armazenam resultados parciais ou precisam de um espaço de trabalho isolado. No entanto, o uso descuidado desses arquivos pode levar a problemas de segurança, sujeira no sistema e comportamentos imprevisíveis. Nesta aula, vamos explorar as ferramentas e técnicas adequadas para criar, utilizar e limpar arquivos temporários de forma robusta e segura em scripts Bash.

Dominar o gerenciamento de arquivos temporários é essencial para escrever scripts profissionais: eles devem ser únicos, ter permissões restritas e ser removidos automaticamente, mesmo em caso de erros ou interrupções. Vamos começar com o comando mktemp, a base para criação segura de temporários, e depois veremos como integrar a limpeza com trap.

mktemp

O comando mktemp é a ferramenta padrão para criar arquivos ou diretórios temporários de forma segura. Ele gera um nome único baseado em um modelo, cria o arquivo com permissões restritas (0600 para arquivos, 0700 para diretórios) e imprime o caminho completo na saída padrão. Isso evita colisões de nomes e ataques do tipo symlink, pois o arquivo é criado com a chamada de sistema O_EXCL.

Sem argumentos, mktemp cria um arquivo em /tmp com um nome aleatório. Você pode especificar um modelo com XXXXXX (pelo menos três X) que será substituído por caracteres aleatórios. Também é possível criar diretórios temporários com a opção -d, que é útil para agrupar vários arquivos temporários.

#!/bin/bash
# Criar um arquivo temporário padrão
temp_file=$(mktemp)
echo "Arquivo temporário criado: $temp_file"

# Criar um arquivo com modelo personalizado em /tmp
temp_file2=$(mktemp /tmp/meu_script.XXXXXX)
echo "Arquivo temporário com modelo: $temp_file2"

# Criar um diretório temporário
temp_dir=$(mktemp -d)
echo "Diretório temporário: $temp_dir"

# Usar o arquivo temporário
echo "Dados temporários" > "$temp_file"
cat "$temp_file"

# Limpeza manual
rm -f "$temp_file" "$temp_file2"
rm -rf "$temp_dir"

Observe que é uma boa prática usar aspas duplas ao redor das variáveis que contêm caminhos, para evitar problemas com espaços ou caracteres especiais. O mktemp também possui opções como -t para usar o diretório padrão do sistema (geralmente /tmp) e -u para gerar um nome sem criar o arquivo (não recomendado, pois há uma janela de segurança).

Limpeza com trap

A limpeza manual com rm é insuficiente em scripts que podem terminar abruptamente devido a erros, sinais ou interrupções do usuário. Para garantir que os arquivos temporários sejam removidos mesmo nesses casos, usamos o comando trap, que permite executar comandos quando o script recebe certos sinais ou quando termina. O sinal EXIT é disparado quando o script termina normalmente ou por um exit explícito, e podemos usá-lo para chamar uma função de limpeza.

Além do EXIT, é comum capturar sinais como INT (Ctrl+C) e TERM (terminação). Definimos um trap para cada sinal que queremos tratar, chamando uma função que remove os temporários. Vamos ver um exemplo completo:

#!/bin/bash

# Diretório temporário global
temp_dir=""

# Função de limpeza
cleanup() {
    echo "Executando limpeza..."
    if [[ -n "$temp_dir" ]]; then
        rm -rf "$temp_dir"
        echo "Diretório temporário removido: $temp_dir"
    fi
}

# Armadilha para EXIT e sinais comuns
trap cleanup EXIT INT TERM

# Criar diretório temporário
temp_dir=$(mktemp -d)
echo "Diretório temporário criado: $temp_dir"

# Simular trabalho
sleep 2

# Se o script for interrompido, o trap será executado
echo "Script finalizado."

Nesse script, se o usuário pressionar Ctrl+C, o sinal INT é capturado, a função cleanup é chamada e o diretório é removido. O mesmo ocorre se o script terminar normalmente. É importante notar que a função de limpeza deve ser idempotente, ou seja, pode ser chamada múltiplas vezes sem causar erros (por exemplo, verificar se a variável está vazia). Também é recomendável usar trap - EXIT se você quiser desativar o trap em algum ponto.

Boas práticas

Ao trabalhar com arquivos temporários, siga estas boas práticas para escrever scripts mais seguros e confiáveis:

  • Sempre use mktemp em vez de criar arquivos com nomes fixos como /tmp/meu_script.tmp. Isso evita colisões e ataques de symlink.
  • Prefira diretórios temporários quando precisar de vários arquivos: crie um diretório com mktemp -d e coloque todos os temporários dentro dele. Assim, a limpeza é feita com um único rm -rf.
  • Defina o trap de limpeza logo no início do script, antes de criar qualquer temporário, para garantir que a limpeza seja registrada.
  • Use variáveis globais ou de ambiente para armazenar os caminhos dos temporários, para que a função de limpeza possa acessá-los.
  • Remova os temporários assim que não forem mais necessários, não apenas no final do script, para liberar espaço e reduzir a janela de exposição.
  • Não use mktemp -u (não criar) porque há uma corrida entre a geração do nome e a criação real, podendo ser explorada.
  • Considere usar trap 'rm -f "$temp_file"' EXIT para casos simples, mas uma função é mais legível e extensível.

Exemplo de script com boas práticas:

#!/bin/bash
set -euo pipefail

# Diretório temporário global
temp_dir=""

cleanup() {
    if [[ -n "$temp_dir" ]]; then
        rm -rf "$temp_dir"
    fi
}
trap cleanup EXIT

# Cria diretório temporário
temp_dir=$(mktemp -d)

# Trabalha dentro do diretório
cd "$temp_dir"
echo "Processando..."
# ...

# O trap limpa automaticamente na saída

Riscos

O uso inadequado de arquivos temporários pode introduzir vários riscos de segurança e robustez:

  • Colisão de nomes: se você usar um nome fixo, outro processo (ou um atacante) pode criar um arquivo com o mesmo nome, levando a sobrescrita de dados ou comportamento inesperado.
  • Ataques de symlink: se o arquivo temporário for criado em um diretório gravável por outros usuários (como /tmp), um atacante pode criar um link simbólico com o nome esperado, apontando para um arquivo sensível. Quando o script escrever no temporário, pode corromper o arquivo alvo.
  • Permissões inadequadas: se o arquivo for criado com permissões muito permissivas, outros usuários podem ler ou modificar os dados temporários, vazando informações ou alterando o resultado do script.
  • Limpeza insuficiente: se o script não remover os temporários, eles se acumulam em /tmp, consumindo espaço e potencialmente causando problemas de disco cheio.
  • Janela de vulnerabilidade: quando você gera um nome e depois cria o arquivo (como em mktemp -u), há um intervalo em que um atacante pode criar um link com esse nome, explorando a corrida.

O mktemp mitiga esses riscos ao criar o arquivo com O_EXCL e permissões restritas, e ao usar nomes aleatórios imprevisíveis. No entanto, ainda é crucial usar trap para limpeza e nunca confiar em nomes fixos.

Referências

Exercícios

  1. Crie um script que usa mktemp para criar um arquivo temporário, escreva a saída do comando date nele e, em seguida, leia e exiba o conteúdo. Faça a limpeza manualmente no final.

    ✓ Resposta:
    #!/bin/bash
    # Cria arquivo temporário
    temp_file=$(mktemp)
    # Escreve a data no arquivo
    date > "$temp_file"
    # Exibe o conteúdo
    cat "$temp_file"
    # Limpeza manual
    rm -f "$temp_file"
    
  2. Escreva um script que crie um diretório temporário e, dentro dele, crie dois arquivos: um com o nome do usuário e outro com o nome do host. Depois, liste o conteúdo do diretório. Certifique-se de remover o diretório ao final.

    ✓ Resposta:
    #!/bin/bash
    # Cria diretório temporário
    temp_dir=$(mktemp -d)
    # Cria arquivos dentro
    user_file="$temp_dir/usuario.txt"
    host_file="$temp_dir/host.txt"
    echo "$USER" > "$user_file"
    echo "$HOSTNAME" > "$host_file"
    # Lista o conteúdo
    ls -l "$temp_dir"
    # Limpeza
    rm -rf "$temp_dir"
    
  3. Modifique o script do exercício 2 para usar trap com a função cleanup que remove o diretório temporário. Teste o script com exit em vários pontos (por exemplo, após criar o diretório, após criar os arquivos).

    ✓ Resposta:
    #!/bin/bash
    
    temp_dir=""
    cleanup() {
        if [[ -n "$temp_dir" ]]; then
            rm -rf "$temp_dir"
            echo "Limpeza executada: $temp_dir"
        fi
    }
    trap cleanup EXIT
    
    temp_dir=$(mktemp -d)
    # Simula trabalho
    echo "Diretório criado: $temp_dir"
    # Ponto de saída antecipada (descomente para testar)
    # exit 1
    
    echo "$USER" > "$temp_dir/usuario.txt"
    echo "$HOSTNAME" > "$temp_dir/host.txt"
    ls -l "$temp_dir"
    # O trap limpa automaticamente ao sair
    
  4. Explique por que usar mktemp -u é perigoso e dê um exemplo de como um atacante poderia explorar essa vulnerabilidade.

    ✓ Resposta:

    mktemp -u gera um nome aleatório e imprime, mas não cria o arquivo. Isso cria uma janela de tempo entre a impressão do nome e a criação real pelo script, onde um atacante pode prever o nome (ou observá-lo) e criar um link simbólico com esse nome apontando para um arquivo sensível (ex.: /etc/passwd). Quando o script tentar criar o arquivo, ele seguirá o symlink e escreverá no arquivo alvo, corrompendo-o ou permitindo que o atacante controle o conteúdo. Por isso, mktemp sem -u cria o arquivo imediatamente com O_EXCL, evitando a corrida.

  5. Escreva um script que simule um processamento longo e, durante o processamento, crie vários arquivos temporários em um diretório temporário. Use trap para limpar tudo, e teste o script enviando o sinal SIGTERM (por exemplo, com kill -TERM).

    ✓ Resposta:
    #!/bin/bash
    
    temp_dir=""
    cleanup() {
        echo "Limpeza em andamento..."
        if [[ -n "$temp_dir" ]]; then
            rm -rf "$temp_dir"
            echo "Removido: $temp_dir"
        fi
    }
    trap cleanup EXIT INT TERM
    
    temp_dir=$(mktemp -d)
    echo "Processando... (PID $$)"
    
    # Cria vários arquivos temporários
    for i in {1..5}; do
        echo "Dados $i" > "$temp_dir/arquivo_$i.tmp"
        sleep 1
    done
    
    # Simula trabalho longo
    sleep 30
    
    echo "Concluído."
    

    Para testar, execute o script em segundo plano, anote o PID e envie kill -TERM <PID>. O trap deve capturar o sinal e limpar o diretório.

Boas práticas e observações finais

Além das boas práticas já mencionadas, lembre-se de que o diretório padrão /tmp é limpo em algumas distribuições na reinicialização, mas não é garantido. Em sistemas críticos, considere usar /var/tmp para arquivos que precisam sobreviver a reinicializações, mas lembre-se de que ele não é limpo automaticamente. Sempre defina permissões restritas nos arquivos temporários, mesmo que o mktemp já o faça, se você modificar o arquivo posteriormente. E, finalmente, teste seus scripts com shellcheck para detectar possíveis problemas.

Com essas técnicas, você estará apto a gerenciar arquivos temporários de forma segura e eficiente em seus scripts Bash. Pratique os exercícios para consolidar o aprendizado e evite os riscos comuns discutidos.