Sinais
Esta aula aborda o gerenciamento de sinais em shell script Linux/Bash, explicando como enviar e tratar sinais, cobrindo comandos como kill e killall, sinais comuns (TERM, KILL, HUP), e técnicas de captura e tratamento de sinais para criar scripts mais robustos e controláveis.
Na jornada de um programador de shell, entender como controlar processos é essencial. Nesta aula, vamos mergulhar no mundo dos sinais do Linux, que são notificações assíncronas enviadas a processos para comunicar eventos ou solicitar ações. Dominar sinais permite que você gerencie processos com precisão, encerre programas de forma limpa, recarregue configurações sem interromper serviços e até mesmo crie scripts que reagem a eventos externos. Vamos explorar desde os comandos básicos de envio até a implementação de tratadores de sinais em seus próprios scripts.
Os sinais são fundamentais no sistema operacional Unix-like. Cada sinal tem um número e um nome simbólico, e seu comportamento padrão pode ser modificado. Nesta aula, você aprenderá a usar kill e killall para enviar sinais, conhecerá os sinais mais comuns (SIGTERM, SIGKILL, SIGHUP) e verá como capturá-los e tratá-los em Bash, tornando seus scripts capazes de responder a solicitações de encerramento, recarga de configuração e muito mais.
kill e killall
O comando kill é a ferramenta clássica para enviar sinais a processos. Apesar do nome, ele não se limita a 'matar' processos; pode enviar qualquer sinal especificado. Sua sintaxe básica é kill [-signal] PID, onde PID é o ID do processo. Se nenhum sinal for especificado, o sinal padrão é SIGTERM (15), que solicita ao processo uma terminação graciosa. Para listar os sinais disponíveis, use kill -l.
Já o killall envia um sinal a todos os processos que correspondem a um determinado nome de comando. Por exemplo, killall firefox envia SIGTERM a todos os processos chamados 'firefox'. Isso é útil quando você não conhece os PIDs ou deseja afetar múltiplas instâncias de um programa. No entanto, é preciso cuidado: o comando pode afetar processos de outros usuários se você não tiver permissões adequadas, e pode ser perigoso se houver processos com nomes semelhantes.
Aqui está um exemplo prático de uso de kill e killall:
#!/bin/bash
# Inicia um processo em segundo plano
sleep 1000 &
# Obtém o PID do último processo em segundo plano
PID=$!
echo "Processo sleep com PID $PID"
# Envia SIGTERM para o processo
kill $PID
# Verifica se o processo ainda está rodando
if kill -0 $PID 2>/dev/null; then
echo "Processo ainda ativo"
else
echo "Processo terminado"
fi
# Exemplo com killall (cuidado!)
# Inicia alguns processos 'sleep'
sleep 500 &
sleep 500 &
# Envia SIGTERM para todos os 'sleep'
killall sleep
Sinais comuns (TERM, KILL, HUP)
Entre os cerca de 30 sinais padrão, alguns são especialmente importantes no dia a dia. O SIGTERM (15) é o sinal padrão do kill. Ele pede ao processo para encerrar, permitindo que ele execute rotinas de limpeza (fechar arquivos, liberar recursos). O SIGKILL (9) é o sinal de 'matar' imediato: não pode ser capturado ou ignorado, e o kernel termina o processo de forma forçada. Use SIGKILL apenas quando um processo não responde ao SIGTERM. O SIGHUP (1) é tradicionalmente enviado quando o terminal que controla o processo é fechado. Muitos daemons (como servidores web) tratam SIGHUP para recarregar suas configurações sem interromper o serviço.
Outros sinais úteis incluem SIGINT (2), gerado ao pressionar Ctrl+C no terminal, e SIGQUIT (3), que gera um core dump. Sinais como SIGSTOP (19) e SIGCONT (18) pausam e retomam processos, respectivamente. Conhecer esses sinais ajuda a entender o comportamento de programas e a escrever scripts que reagem adequadamente.
A tabela a seguir resume os sinais mais comuns:
| Sinal | Número | Comportamento padrão | Uso típico |
|---|---|---|---|
| SIGHUP | 1 | Terminar | Recarregar configurações |
| SIGINT | 2 | Terminar | Interrupção (Ctrl+C) |
| SIGQUIT | 3 | Terminar com core dump | Diagnóstico |
| SIGKILL | 9 | Terminar (não capturável) | Forçar término |
| SIGTERM | 15 | Terminar | Terminação graciosa |
| SIGSTOP | 19 | Parar | Pausar processo |
| SIGCONT | 18 | Continuar | Retomar processo |
Enviando sinais
Além de kill e killall, você pode enviar sinais de outras formas. O comando pkill permite enviar sinais a processos que correspondem a padrões de nome ou outras condições, semelhante ao killall mas com mais flexibilidade (por exemplo, pkill -u usuario para matar processos de um usuário). O comando kill também aceita nomes de sinais em vez de números, como kill -HUP 1234.
No shell, você pode enviar sinais a processos em segundo plano usando kill com o PID obtido via $! (PID do último processo em background). Lembre-se de que você só pode enviar sinais a processos que você possui (mesmo usuário) ou que pertencem a outros usuários se você tiver privilégios de root. Para verificar se um processo existe, use kill -0 PID, que envia um sinal nulo apenas para testar a existência.
Exemplo de envio de sinais com kill e pkill:
#!/bin/bash
# Inicia um processo em background
sleep 2000 &
PID=$!
echo "PID: $PID"
# Verifica se o processo existe
if kill -0 $PID; then
echo "Processo existe"
fi
# Envia SIGTERM
kill $PID
# Envia SIGKILL (se necessário)
# kill -9 $PID
# Envia SIGHUP para todos os processos 'nginx' (se existirem)
pkill -HUP nginx
Tratamento
Em scripts Bash, você pode capturar sinais usando o comando trap. A sintaxe é trap 'comandos' SINAIS, onde comandos são executados quando um dos sinais especificados é recebido. Isso permite que você execute limpezas, salve estados ou ignore sinais. Por exemplo, para ignorar SIGINT (Ctrl+C) e continuar a execução, use trap '' SIGINT. Para restaurar o comportamento padrão, use trap - SIGINT.
É importante notar que o trap só funciona para sinais que podem ser capturados (não para SIGKILL ou SIGSTOP). Além disso, ao usar trap com funções, você pode criar tratadores mais complexos. Por exemplo, para executar uma função de limpeza ao receber SIGTERM ou SIGINT, você pode definir:
#!/bin/bash
# Função de limpeza
cleanup() {
echo "Executando limpeza..."
# Remover arquivos temporários, etc.
exit 0
}
# Armadilha para SIGINT e SIGTERM
trap cleanup SIGINT SIGTERM
echo "Script rodando. Pressione Ctrl+C para testar."
while true; do
sleep 1
done
Outro uso comum é recarregar configurações com SIGHUP. Em scripts longos ou daemons, você pode capturar SIGHUP para reler um arquivo de configuração. Lembre-se de que o trap é interpretado no momento em que o sinal é recebido, então variáveis e funções devem estar definidas antes. Além disso, se você tiver sub-shells, o trap não é herdado, então você precisa defini-lo em cada sub-shell se necessário.
Boas práticas: sempre capture SIGTERM e SIGINT para encerrar seu script de forma limpa, liberando recursos. Evite usar SIGKILL como primeira opção, pois não permite limpeza. Teste seus tratadores em ambientes controlados para garantir que não haja efeitos colaterais.
Referências
- man 7 signal - Linux manual page
- Bash Reference Manual: Signals
- kill(1) - Linux manual page
- killall(1) - Linux manual page
- pkill(1) - Linux manual page
- Bash Builtins: trap
Exercícios
- Escreva um script que inicie um processo
sleep 1000em segundo plano, capture seu PID e envie SIGTERM usandokill. Verifique se o processo terminou. - Crie um script que use
trappara capturar SIGINT e imprimir uma mensagem amigável antes de sair. Teste pressionando Ctrl+C. - Explique a diferença entre SIGKILL e SIGTERM e dê um exemplo de quando usar cada um.
- Escreva um script que capture SIGHUP e recarregue uma variável de configuração de um arquivo.
- Use
killallpara enviar SIGTERM a todos os processos chamados 'sleep' que você iniciou. Cuidado com processos de outros usuários.
#!/bin/bash
sleep 1000 &
PID=$!
echo "PID: $PID"
kill $PID
if kill -0 $PID 2>/dev/null; then
echo "Processo ainda ativo"
else
echo "Processo terminado"
fi
#!/bin/bash
cleanup() {
echo "\nRecebido SIGINT. Encerrando..."
exit 0
}
trap cleanup SIGINT
echo "Pressione Ctrl+C para testar."
while true; do
sleep 1
done
kill PID para terminar graciosamente; kill -9 PID se ele travar.#!/bin/bash
CONFIG_FILE="config.txt"
VALUE="default"
load_config() {
if [[ -f $CONFIG_FILE ]]; then
VALUE=$(cat $CONFIG_FILE)
echo "Configuração carregada: $VALUE"
fi
}
load_config
trap load_config SIGHUP
echo "Aguardando SIGHUP..."
while true; do
sleep 5
done
#!/bin/bash
# Inicia alguns processos sleep
sleep 300 &
sleep 300 &
# Envia SIGTERM para todos os processos 'sleep'
killall sleep
# Verifica se ainda há processos sleep
pgrep -l sleep || echo "Nenhum processo sleep restante"
Observações Finais
O gerenciamento de sinais é uma habilidade poderosa no shell scripting. Dominar o envio e o tratamento de sinais permite criar scripts mais interativos, robustos e que se integram bem ao sistema. Lembre-se de sempre testar seus scripts em ambientes controlados e de usar sinais de forma ética, evitando matar processos de outros usuários sem permissão. Pratique com os exercícios e explore a documentação para aprofundar seu conhecimento.