trap e sinais
A aula aborda o comando trap no Bash para capturar e tratar sinais como EXIT, INT e TERM, permitindo executar ações de limpeza e encerramento seguro de scripts. São discutidos casos de uso práticos, como remoção de arquivos temporários e tratamento de interrupções, com exemplos detalhados.
Em scripts shell, é comum precisarmos executar ações específicas quando o script termina, seja de forma normal ou devido a um sinal externo (como Ctrl+C). O comando trap permite capturar sinais do sistema e executar comandos ou funções personalizadas. Isso é essencial para garantir a limpeza de recursos temporários, evitar arquivos órfãos e assegurar um comportamento previsível mesmo em interrupções.
Nesta aula, você aprenderá a usar trap para lidar com os sinais mais comuns (EXIT, INT, TERM), criar funções de limpeza e aplicar boas práticas em scripts do mundo real.
Capturando sinais (EXIT, INT, TERM)
O trap associa um comando ou função a um ou mais sinais. A sintaxe básica é: trap 'comando' SINAL. O sinal EXIT é especial: é gerado sempre que o script termina, independentemente da causa (sucesso, erro, saída por exit ou fim natural). Já INT (SIGINT) é enviado ao pressionar Ctrl+C, e TERM (SIGTERM) é o sinal padrão de término enviado pelo comando kill.
É importante notar que o comando dentro de trap pode ser uma string com múltiplos comandos separados por ponto e vírgula, ou uma função. Para maior clareza, recomenda-se o uso de funções. Exemplo:
#!/bin/bash
cleanup() {
echo "Limpando recursos..."
rm -f /tmp/meu_arquivo_tmp
}
trap cleanup EXIT INT TERM
echo "Script em execução..."
sleep 10
echo "Fim normal."No código acima, a função cleanup é chamada automaticamente se o script receber qualquer um dos sinais EXIT, INT ou TERM. Isso garante que o arquivo temporário seja removido mesmo se o script for interrompido.
Limpeza de temporários
Arquivos temporários são comuns em scripts que processam dados, fazem downloads ou realizam operações intermediárias. Se o script falhar ou for interrompido, esses arquivos podem acumular-se e consumir espaço em disco. Usando trap, podemos removê-los de forma confiável.
Uma prática recomendada é criar um diretório temporário com mktemp -d e registrar sua remoção no trap. Exemplo:
#!/bin/bash
# Cria diretório temporário
tmp_dir=$(mktemp -d)
# Função de limpeza
cleanup() {
echo "Removendo $tmp_dir"
rm -rf "$tmp_dir"
}
trap cleanup EXIT
# Usa o diretório temporário
cd "$tmp_dir" || exit 1
echo "Trabalhando em $tmp_dir"
touch arquivo1.txt arquivo2.txt
# ... processamento ...
echo "Script concluído."Note que o trap foi configurado apenas para EXIT. Se o script receber um sinal INT ou TERM, o EXIT será gerado automaticamente após o tratamento padrão do sinal (a menos que o trap também capture esses sinais). Portanto, capturar EXIT já cobre a maioria dos casos de término.
Casos de uso
Além da limpeza de temporários, o trap é útil para:
- Restaurar configurações: Se o script altera variáveis de ambiente, diretórios ou estados, pode restaurá-los ao final.
- Encerrar processos filhos: Em scripts que disparam subprocessos, o trap pode matá-los antes de sair.
- Log de finalização: Registrar em um arquivo de log que o script terminou, com timestamp e status.
- Ignorar sinais temporariamente: Para evitar interrupções durante operações críticas (ex.: escrita em arquivo).
- Tratamento personalizado de Ctrl+C: Em vez de simplesmente sair, pode-se perguntar ao usuário se deseja realmente cancelar.
Cada sinal pode ter um tratamento diferente. Por exemplo, pode-se ignorar SIGINT durante uma operação atômica e restaurá-lo depois:
#!/bin/bash
# Ignora Ctrl+C durante operação crítica
trap '' INT
echo "Operação crítica em andamento..."
sleep 5
echo "Operação concluída."
# Restaura tratamento padrão
trap - INT
echo "Agora Ctrl+C funciona novamente."
sleep 5Exemplos
Exemplo 1: Script de backup que remove arquivos temporários e registra log.
#!/bin/bash
log_file="/var/log/backup.log"
temp_dir=$(mktemp -d)
cleanup() {
echo "$(date): Limpeza do backup" >> "$log_file"
rm -rf "$temp_dir"
}
trap cleanup EXIT
# Simula backup
cp -r /dados/importantes "$temp_dir"
echo "Backup copiado."
# ... compactação ...
echo "$(date): Backup concluído" >> "$log_file"Exemplo 2: Script interativo que pergunta antes de sair com Ctrl+C.
#!/bin/bash
handle_int() {
echo
read -p "Deseja realmente sair? (s/N) " resposta
if [[ "$resposta" =~ ^[Ss]$ ]]; then
echo "Saindo..."
exit 0
else
echo "Continuando..."
fi
}
trap handle_int INT
echo "Pressione Ctrl+C para testar."
while true; do
sleep 1
doneExemplo 3: Garantir que processos filhos sejam mortos ao sair.
#!/bin/bash
# Inicia um processo em segundo plano
sleep 100 &
pid=$!
cleanup() {
echo "Matando processo $pid"
kill $pid 2>/dev/null
wait $pid 2>/dev/null
}
trap cleanup EXIT
echo "Processo $pid em execução. Pressione Ctrl+C para encerrar."
sleep 10
echo "Script terminou normalmente."Boas práticas
- Use funções para maior clareza e reutilização.
- Sempre use aspas duplas nas variáveis para evitar problemas com espaços.
- Em traps que capturam INT, considere redefinir o trap para o padrão após a operação crítica.
- Teste o comportamento do script com diferentes sinais usando
kill -SINAL PID. - Documente no script quais sinais estão sendo capturados e por quê.
Referências
- GNU Bash Manual: trap
- Bash Beginners Guide: Traps
- Greg's Wiki: SignalTrap
- Shell Scripting Tutorial: Trap
- Stack Overflow: Killing background processes on exit
- man7.org: signal(7)
Exercícios
Crie um script que crie um arquivo temporário em /tmp e, ao receber EXIT, o remova. Teste com
exit 0eexit 1.✓ Resposta:#!/bin/bash tmpfile="/tmp/meu_temp_$$" touch "$tmpfile" cleanup() { rm -f "$tmpfile"; echo "Arquivo removido."; } trap cleanup EXIT echo "Arquivo criado: $tmpfile" exit 0Escreva um script que ignore Ctrl+C durante 5 segundos (usando trap com string vazia) e depois restaure o comportamento padrão.
✓ Resposta:#!/bin/bash echo "Ignorando Ctrl+C por 5s..." trap '' INT sleep 5 echo "Agora Ctrl+C funciona." trap - INT sleep 5 echo "Fim."Crie um script que, ao receber SIGTERM, imprima "Recebi SIGTERM, limpando..." e saia com código 0.
✓ Resposta:#!/bin/bash cleanup() { echo "Recebi SIGTERM, limpando..."; exit 0; } trap cleanup TERM echo "PID: $$. Envie 'kill $$' para testar." while true; do sleep 1; doneFaça um script que crie um diretório temporário com mktemp -d, execute um loop infinito e, ao ser interrompido (qualquer sinal), remova o diretório e saia.
✓ Resposta:#!/bin/bash tmpdir=$(mktemp -d) cleanup() { rm -rf "$tmpdir"; echo "Diretório $tmpdir removido."; exit; } trap cleanup EXIT INT TERM echo "Diretório criado: $tmpdir" while true; do sleep 1; doneEscreva um script que, ao receber SIGINT, pergunte "Deseja sair? (s/N)" e só saia se o usuário responder 's' ou 'S'. Use trap.
✓ Resposta:#!/bin/bash handle_int() { echo read -p "Deseja sair? (s/N) " resp if [[ "$resp" =~ ^[Ss]$ ]]; then echo "Saindo..." exit 0 fi } trap handle_int INT echo "Pressione Ctrl+C para testar." while true; do sleep 1; done