trap e sinais
Esta aula ensina como usar o comando trap no Bash para capturar sinais do sistema, como INT, TERM e EXIT, permitindo executar ações de limpeza ao final do script ou em interrupções. São abordados casos de uso comuns, como remoção de arquivos temporários, e exemplos práticos para consolidar o aprendizado.
Em scripts shell, muitas vezes precisamos garantir que ações de limpeza sejam executadas mesmo se o script for interrompido inesperadamente. O comando trap permite capturar sinais do sistema operacional e executar comandos personalizados quando esses sinais são recebidos. Isso é essencial para criar scripts robustos que lidam com arquivos temporários, conexões de rede ou outros recursos que precisam ser liberados.
Os sinais são notificações enviadas a um processo para indicar eventos como interrupção do usuário (Ctrl+C), término forçado ou finalização normal. Com trap, podemos definir handlers (manipuladores) que respondem a esses sinais, executando funções de limpeza ou ignorando certos sinais quando necessário.
Capturando sinais (EXIT, INT, TERM)
O comando trap tem a sintaxe: trap 'comandos' SIGNAME. Onde 'comandos' são executados quando o sinal SIGNAME é recebido. Os sinais mais comuns em scripts são:
- EXIT: Não é um sinal real, mas um pseudo-sinal que é acionado quando o script termina, seja por conclusão normal, por um
exitou por um sinal fatal. É ideal para limpeza geral. - INT: Interrupção (geralmente Ctrl+C). Permite ao script finalizar graciosamente ao invés de abortar abruptamente.
- TERM: Sinal de término enviado pelo comando
killou pelo sistema. Capturá-lo permite limpeza antes de sair.
Exemplo básico de captura de INT e TERM:
#!/bin/bash
cleanup() {
echo "Executando limpeza..."
rm -f /tmp/meu_arquivo_temporario
exit 0
}
trap cleanup INT TERM
echo "Script em execução. Pressione Ctrl+C para interromper."
while true; do
sleep 1
doneNesse exemplo, a função cleanup é chamada quando o script recebe SIGINT ou SIGTERM. O exit 0 dentro da função garante que o script termine após a limpeza.
Para capturar a saída normal (EXIT), usamos:
trap cleanup EXITIsso garante que cleanup seja executada mesmo se o script chegar ao fim naturalmente.
Limpeza de temporários
Um uso clássico de trap é remover arquivos temporários criados durante a execução do script. Sem a captura de sinais, se o script for interrompido, esses arquivos podem ficar acumulados no sistema.
Exemplo de script que cria um diretório temporário e o remove ao final:
#!/bin/bash
TEMPDIR=$(mktemp -d)
cleanup() {
echo "Removendo diretório temporário $TEMPDIR"
rm -rf "$TEMPDIR"
}
trap cleanup EXIT
echo "Diretório temporário criado em: $TEMPDIR"
# Simula trabalho
sleep 10Mesmo que o script seja interrompido com Ctrl+C, o trap EXIT será executado? Na verdade, o sinal INT não dispara EXIT automaticamente. Para garantir a limpeza também em interrupções, devemos capturar INT explicitamente:
trap cleanup EXIT INT TERMDessa forma, qualquer sinal ou término normal chamará a função de limpeza.
Casos de uso
Além da remoção de temporários, trap é útil em várias situações:
- Ignorar sinais: Para evitar que um script seja interrompido, podemos ignorar certos sinais usando
trap '' SIGINT. - Reinicializar configurações: Restaurar configurações de terminal (como echo) ao sair.
- Matar processos filhos: Se o script inicia processos em segundo plano, podemos usar trap para terminá-los antes de sair.
- Registrar logs: Escrever em um arquivo de log quando o script termina.
- Executar comandos apenas na saída bem-sucedida: Combinar com
trap ... EXITpara ações que devem ocorrer independentemente do motivo da saída.
Exemplo de ignorar Ctrl+C:
#!/bin/bash
trap '' INT
echo "Ctrl+C não funciona agora. Pressione Ctrl+\ para sair."
sleep 30Exemplos
Vamos a um exemplo mais completo que combina múltiplos sinais e funções:
#!/bin/bash
LOGFILE="/var/log/meu_script.log"
TEMPDIR=$(mktemp -d)
cleanup() {
local exit_code=$?
echo "$(date): Script finalizado com código $exit_code" >> "$LOGFILE"
rm -rf "$TEMPDIR"
exit $exit_code
}
handle_int() {
echo "Interrupção recebida. Finalizando..."
exit 1
}
trap cleanup EXIT
trap handle_int INT TERM
echo "Iniciando script. PID: $$"
# Simula trabalho
for i in {1..10}; do
echo "Trabalhando... $i"
sleep 1
doneNesse script, a função cleanup é chamada sempre que o script termina (EXIT) e registra o código de saída em um log. Já handle_int lida com interrupções e termina com código 1. A variável $? captura o código de saída do último comando executado.
Outro exemplo útil é garantir que um processo em segundo plano seja morto ao sair:
#!/bin/bash
# Inicia um servidor em background
./meu_servidor &
SERVER_PID=$!
cleanup() {
echo "Parando servidor (PID $SERVER_PID)..."
kill $SERVER_PID 2>/dev/null
wait $SERVER_PID 2>/dev/null
echo "Servidor parado."
}
trap cleanup EXIT INT TERM
echo "Servidor iniciado. Pressione Ctrl+C para parar."
wait $SERVER_PIDO wait aguarda o processo em segundo plano terminar. Se o script receber um sinal, o trap executa a limpeza e o wait é interrompido.
Boas práticas e observações finais
Ao usar trap, lembre-se de:
- Escapar corretamente aspas e variáveis dentro do comando do trap. Use aspas simples para evitar expansão prematura ou aspas duplas com cuidado.
- Definir traps no início do script, antes de qualquer operação que possa gerar arquivos temporários.
- Usar
trap - SIGNAMEpara resetar o trap para o comportamento padrão. - Testar o script com diferentes sinais para garantir que a limpeza funciona.
O comando trap é uma ferramenta poderosa para escrever scripts confiáveis e profissionais. Dominá-lo é essencial para qualquer programador shell.
Referências
- Documentação oficial do Bash: trap
- Bash Guide for Beginners: Trapping signals
- Greg's Wiki: Signal Trap
- Shell Scripting Tutorial: Trap
- Linux man page: signal(7)
Exercícios
Crie um script que cria um arquivo temporário em /tmp, escreve algo nele e, ao sair (qualquer motivo), o remove. Use trap para capturar EXIT.
✓ Resposta:#!/bin/bash TMPFILE=$(mktemp /tmp/meu_script.XXXXXX) echo "Conteúdo temporário" > "$TMPFILE" cleanup() { rm -f "$TMPFILE" echo "Arquivo $TMPFILE removido." } trap cleanup EXIT echo "Arquivo criado: $TMPFILE" # Resto do script sleep 5Escreva um script que ignora o sinal INT (Ctrl+C) por 10 segundos e depois volta a tratá-lo normalmente.
✓ Resposta:#!/bin/bash echo "Ignorando Ctrl+C por 10 segundos..." trap '' INT sleep 10 echo "Agora Ctrl+C funcionará." trap - INT sleep 10 echo "Fim."Faça um script que inicia um processo em segundo plano (ex.: sleep 100) e, ao receber INT ou TERM, mata esse processo antes de sair.
✓ Resposta:#!/bin/bash sleep 100 & PID=$! cleanup() { echo "Matando processo $PID..." kill $PID 2>/dev/null wait $PID 2>/dev/null echo "Processo finalizado." exit 0 } trap cleanup INT TERM echo "Processo em segundo plano iniciado (PID $PID)." wait $PID echo "Processo terminou sozinho."Crie um script que registra em um arquivo de log (/tmp/script.log) a data e hora de início e de término, usando trap EXIT. Dica: capture a data de início em uma variável.
✓ Resposta:#!/bin/bash LOGFILE="/tmp/script.log" START_TIME=$(date) echo "Início: $START_TIME" >> "$LOGFILE" cleanup() { echo "Término: $(date)" >> "$LOGFILE" } trap cleanup EXIT echo "Script executando..." sleep 5Escreva um script que cria dois arquivos temporários, e na limpeza (EXIT, INT, TERM) remove ambos. Use uma função de limpeza que itera sobre uma lista de arquivos.
✓ Resposta:#!/bin/bash TMP1=$(mktemp /tmp/tmp1.XXXXXX) TMP2=$(mktemp /tmp/tmp2.XXXXXX) FILES=("$TMP1" "$TMP2") cleanup() { for f in "${FILES[@]}"; do rm -f "$f" echo "Removido: $f" done } trap cleanup EXIT INT TERM echo "Arquivos criados: ${FILES[*]}" sleep 10