trap - Manipulacao de Sinais e Limpeza no Bash

trap - Manipulacao de Sinais e Limpeza no Bash

O Que Voce Vai Aprender

  • Como usar trap para capturar sinais e garantir que a limpeza seja executada
  • Como prevenir o bug classico onde Ctrl-C deixa arquivos temporarios para tras
  • Um template pronto para producao combinando o pseudo-sinal EXIT, uma funcao cleanup e set -e

Resumo Rapido

  • Canalize toda limpeza em um unico trap cleanup EXIT (ele roda nao importa como o script termine)
  • Crie arquivos temporarios com mktemp, depois defina o trap imediatamente
  • SIGKILL (9) e SIGSTOP nao podem ser capturados. Trate tudo o mais.

Pre-requisitos

  • Shell: bash (EXIT / INT / TERM tambem funcionam em POSIX sh)
  • Publico: usuarios intermediarios escrevendo scripts shell

O Que E o trap?

Conclusao: trap e um builtin do bash que registra um comando para executar quando um sinal ou evento especial ocorre. E como voce agenda limpeza.

trap registra um manipulador de interrupcao: "quando este sinal chegar, execute este comando." A sintaxe basica e:

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 numero 2. O prefixo SIG e opcional, e para portabilidade o nome simples (INT / TERM / EXIT) e preferido.

O primeiro argumento e uma string que o shell avalia. Aspas simples adiam a expansao ate o sinal realmente chegar. Aspas duplas expandem variaveis quando o trap e definido, entao seja deliberado sobre aspas ao embutir variaveis de limpeza.

Por Que Usar o Pseudo-sinal EXIT?

Conclusao: EXIT e um pseudo-sinal que dispara no instante em que um script termina, seja em sucesso, erro ou Ctrl-C. Canalizar a limpeza aqui e a abordagem mais robusta.

Capturar sinais reais (INT / TERM) um por um arrisca perder um e pular a limpeza. EXIT reage ao evento "script esta terminando" em vez de um sinal, entao 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 nao 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 Voce Escreve uma Funcao de Cleanup?

Conclusao: Colete a limpeza em uma funcao cleanup e registre-a com trap cleanup EXIT. Mesmo com muitos recursos temporarios, voce gerencia a desmontagem em um unico lugar.

Uma string de comando inline e suficiente para trabalho curto, mas a legibilidade sofre conforme a lista de coisas para remover cresce. Extrair uma funcao e a pratica padrao.

#!/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 e local rc=$? no topo da funcao. Executar outros comandos dentro de cleanup sobrescreve $?, entao voce salva o codigo de saida primeiro e o restaura com exit "$rc". Isso significa "limpe, mas ainda reporte o erro real para quem chamou."

Criar um diretorio de trabalho com mktemp -d e remove-lo com rm -rf "$workdir" e mais seguro do que rastrear arquivos temporarios individuais. Um workdir vazio tornaria rm -rf perigoso, entao set -u (erro em variaveis indefinidas) previne esse problema.

Como Voce Trata Multiplos Sinais?

Conclusao: Canalize a limpeza em EXIT, e so capture INT / TERM individualmente quando precisar de comportamento de interrupcao personalizado. Alguns sinais nao podem ser capturados.

Aqui estao os principais sinais e seus usos.

Sinal Numero Disparado por Capturavel
INT 2 Ctrl-C Sim
TERM 15 kill padrao Sim
HUP 1 Desconexao do terminal Sim
QUIT 3 Ctrl-\ Sim
EXIT 0 Script termina (pseudo) Sim
KILL 9 kill -9 Nao
STOP 19 Suspensao de processo Nao

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 entao dispara e remove tmpfile. Por convencao, o codigo de saida para INT e 128 + 2 = 130.

SIGKILL (9) e SIGSTOP (19) nao podem ser capturados, ignorados ou redefinidos por design do SO. O requisito "limpar mesmo com kill -9" e impossivel com trap. Trate isso de fora, ex. um processo supervisor ou ExecStopPost= do systemd.

Como Voce Combina com set -e de Forma Segura?

Conclusao: 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, entao a limpeza e garantida. Adicionar o pseudo-sinal ERR (uma extensao 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 padrao de dois estagios que e a base de um script defensivo.

Adicionar set -o pipefail tambem detecta falhas no meio de um pipeline. set -euo pipefail + trap cleanup EXIT vale memorizar como o template de fortalecimento para scripts shell.

Como Voce Inspeciona ou Remove um Trap?

Conclusao: Liste traps ativos com trap -p, e remova um com trap - SINAL. Ambos sao uteis 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 especifico, defina seu comando como -.

trap - EXIT       # remover o trap EXIT (restaurar comportamento padrao)

Para ignorar um sinal (nao fazer nada quando ele chegar), defina o comando como uma string vazia.

trap '' INT       # desabilitar Ctrl-C

Definir trap '' INT para ignorar e herdado pelos processos filhos. Se voce so quer desabilitar Ctrl-C em parte de um script, restaure com trap - INT ao sair da secao critica.

Resumo: o Template Seguro de trap

Conclusao: Crie recursos temporarios com mktemp, defina trap cleanup EXIT imediatamente, e faca o cleanup salvar o codigo de saida antes de desmontar. Este e o padrao que nao 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"

Tres pontos para lembrar:

  • Canalize a limpeza em EXIT (ele roda em sucesso, falha e interrupcao)
  • Salve o codigo de saida no topo de cleanup com local rc=$?, depois exit "$rc"
  • SIGKILL / SIGSTOP nao podem ser capturados. Projete com isso em mente.

Como proximo passo, tente escrever um script pratico baseado em trap (gerenciamento de lock-file, um job agendado) junto com Basico do getopts ou Basico do Cron para aprofundar seu entendimento.

Proximas Leituras