trap - Manipulação de Sinais e Limpeza no Bash
O Que Você Vai Aprender
- Como usar
trappara capturar sinais e garantir que a limpeza seja executada - Como prevenir o bug clássico onde Ctrl-C deixa arquivos temporários para trás
- Um template pronto para produção combinando o pseudo-sinal
EXIT, uma funçãocleanupeset -e
Resumo Rápido
- Canalize toda limpeza em um único
trap cleanup EXIT(ele roda não importa como o script termine) - Crie arquivos temporários com
mktemp, depois defina otrapimediatamente SIGKILL(9) eSIGSTOPnão podem ser capturados. Trate tudo o mais.
Pré-requisitos
- Shell: bash (
EXIT/INT/TERMtambém funcionam em POSIX sh) - Público: usuários intermediários escrevendo scripts shell
O Que É o trap?
Conclusão: trap é um builtin do bash que registra um comando para executar quando um sinal ou evento especial ocorre. É como você agenda limpeza.
trap registra um manipulador de interrupção: "quando este sinal chegar, execute este comando." A sintaxe básica é:
trap 'comando' SINAL...
Por exemplo, capturando Ctrl-C (SIGINT) para imprimir uma mensagem:
trap 'echo "Interrupted"' INT
Um sinal pode ser nomeado SIGINT, INT, ou dado por número 2. O prefixo SIG é opcional, e para portabilidade o nome simples (INT / TERM / EXIT) é preferido.
O primeiro argumento é uma string que o shell avalia. Aspas simples adiam a expansão até o sinal realmente chegar. Aspas duplas expandem variáveis quando o trap é definido, então seja deliberado sobre aspas ao embutir variáveis de limpeza.
Por Que Usar o Pseudo-sinal EXIT?
Conclusão: EXIT é um pseudo-sinal que dispara no instante em que um script termina, seja em sucesso, erro ou Ctrl-C. Canalizar a limpeza aqui é a abordagem mais robusta.
Capturar sinais reais (INT / TERM) um por um arrisca perder um e pular a limpeza. EXIT reage ao evento "script está terminando" em vez de um sinal, então ele roda exatamente uma vez independentemente de como o script termine.
#!/usr/bin/env bash tmpfile=$(mktemp) trap 'rm -f "$tmpfile"' EXIT # O que quer que aconteca depois (saida normal, falha com set -e, Ctrl-C), # tmpfile e sempre removido quando o script termina. echo "working..." > "$tmpfile" cat "$tmpfile"
O trap EXIT não roda com SIGKILL (kill -9). SIGKILL encerra o processo imediatamente, sem dar chance de executar um manipulador. Entenda isso como um limite rigido do trap.
Como Você Escreve uma Função de Cleanup?
Conclusão: Colete a limpeza em uma função
cleanupe registre-a comtrap cleanup EXIT. Mesmo com muitos recursos temporários, você gerencia a desmontagem em um único lugar.
Uma string de comando inline é suficiente para trabalho curto, mas a legibilidade sofre conforme a lista de coisas para remover cresce. Extrair uma função é a prática padrão.
#!/usr/bin/env bash
set -euo pipefail
workdir=$(mktemp -d)
lockfile="/tmp/myjob.lock"
cleanup() {
local rc=$? # salvar o codigo de saida anterior
rm -rf "$workdir"
rm -f "$lockfile"
echo "cleanup done (exit=$rc)"
exit "$rc" # sair com o codigo original
}
trap cleanup EXIT
# --- trabalho principal ---
touch "$lockfile"
echo "data" > "$workdir/output.txt"A linha chave é local rc=$? no topo da função. Executar outros comandos dentro de cleanup sobrescreve $?, então você salva o código de saída primeiro e o restaura com exit "$rc". Isso significa "limpe, mas ainda reporte o erro real para quem chamou."
Criar um diretório de trabalho com mktemp -d e removê-lo com rm -rf "$workdir" e mais seguro do que rastrear arquivos temporários individuais. Um workdir vazio tornaria rm -rf perigoso, então set -u (erro em variáveis indefinidas) previne esse problema.
Como Você Trata Múltiplos Sinais?
Conclusão: Canalize a limpeza em EXIT, e só capture INT / TERM individualmente quando precisar de comportamento de interrupção personalizado. Alguns sinais não podem ser capturados.
Aqui estão os principais sinais e seus usos.
| Sinal | Número | Disparado por | Capturável |
|---|---|---|---|
INT |
2 | Ctrl-C | Sim |
TERM |
15 | kill padrão |
Sim |
HUP |
1 | Desconexão do terminal | Sim |
QUIT |
3 | Ctrl-\ | Sim |
EXIT |
0 | Script termina (pseudo) | Sim |
KILL |
9 | kill -9 |
Não |
STOP |
19 | Suspensão de processo | Não |
Imprima uma mensagem personalizada em INT enquanto deixa a desmontagem para EXIT:
#!/usr/bin/env bash tmpfile=$(mktemp) trap 'rm -f "$tmpfile"' EXIT trap 'echo "Interrupted by user"; exit 130' INT echo "Running... press Ctrl-C to interrupt" sleep 30
Quando o manipulador INT encerra o script com exit 130, o trap EXIT então dispara e remove tmpfile. Por convenção, o código de saída para INT é 128 + 2 = 130.
SIGKILL (9) e SIGSTOP (19) não podem ser capturados, ignorados ou redefinidos por design do SO. O requisito "limpar mesmo com kill -9" é impossível com trap. Trate isso de fora, ex. um processo supervisor ou ExecStopPost= do systemd.
Como Você Combina com set -e de Forma Segura?
Conclusão: set -e (sair em erro) combina bem com trap. Mesmo se um comando falhar no meio, o trap executa a limpeza, sem deixar estado incompleto.
Com set -e, o script sai no momento em que um comando falha. O trap EXIT ainda roda, então a limpeza é garantida. Adicionar o pseudo-sinal ERR (uma extensão do bash) permite registrar qual comando falhou.
#!/usr/bin/env bash set -euo pipefail workdir=$(mktemp -d) trap 'rm -rf "$workdir"' EXIT trap 'echo "error: failed at line $LINENO (exit=$?)" >&2' ERR cp /etc/hostname "$workdir/" grep "nonexistent string" "$workdir/hostname" # falha aqui -> ERR e EXIT disparam echo "never reached"
O trap ERR reporta a falha e o trap EXIT limpa - um padrão de dois estágios que é a base de um script defensivo.
Adicionar set -o pipefail também detecta falhas no meio de um pipeline. set -euo pipefail + trap cleanup EXIT vale memorizar como o template de fortalecimento para scripts shell.
Como Você Inspeciona ou Remove um Trap?
Conclusão: Liste traps ativos com trap -p, e remova um com trap - SINAL. Ambos são úteis ao debugar.
Liste os traps atualmente definidos com trap -p.
$ trap 'rm -f /tmp/x' EXIT $ trap -p trap -- 'rm -f /tmp/x' EXIT
Para remover um trap para um sinal específico, defina seu comando como -.
trap - EXIT # remover o trap EXIT (restaurar comportamento padrao)
Para ignorar um sinal (não fazer nada quando ele chegar), defina o comando como uma string vazia.
trap '' INT # desabilitar Ctrl-C
Definir trap '' INT para ignorar é herdado pelos processos filhos. Se você só quer desabilitar Ctrl-C em parte de um script, restaure com trap - INT ao sair da seção crítica.
Resumo: o Template Seguro de trap
Conclusão: Crie recursos temporários com mktemp, defina trap cleanup EXIT imediatamente, e faça o cleanup salvar o código de saída antes de desmontar. Este é o padrão que não falha.
Copiar e colar: esqueleto de um script robusto
#!/usr/bin/env bash
set -euo pipefail
workdir=$(mktemp -d)
cleanup() {
local rc=$?
rm -rf "$workdir"
exit "$rc"
}
trap cleanup EXIT
trap 'echo "interrupted" >&2; exit 130' INT
# --- trabalho principal vai aqui ---
echo "scratch" > "$workdir/tmp.txt"Três pontos para lembrar:
- Canalize a limpeza em
EXIT(ele roda em sucesso, falha e interrupção) - Salve o código de saída no topo de
cleanupcomlocal rc=$?, depoisexit "$rc" SIGKILL/SIGSTOPnão podem ser capturados. Projete com isso em mente.
Como próximo passo, tente escrever um script prático baseado em trap (gerenciamento de lock-file, um job agendado) junto com Básico do getopts ou Básico do Cron para aprofundar seu entendimento.