<h2>Entendendo Processos em Shell</h2>
<p>Um processo em Unix/Linux é uma instância de um programa em execução. Quando você executa um comando no shell, o sistema operacional cria um novo processo com um identificador único chamado PID (Process ID). Compreender como os processos funcionam é fundamental para criar scripts robustos, pois você precisa saber como iniciar, monitorar e finalizar processos corretamente.</p>
<p>Cada processo possui um processo pai (PPID) e pode gerar processos filhos. Quando um script shell é executado, ele se torna o processo pai de todos os comandos executados dentro dele. É crucial entender essa hierarquia porque quando o processo pai é finalizado, o comportamento dos filhos depende de como foram inicializados — alguns podem se tornar órfãos, outros podem ser automaticamente encerrados.</p>
<h3>Executando Processos em Primeiro e Segundo Plano</h3>
<p>No shell, você pode executar um comando em primeiro plano (foreground) ou segundo plano (background). Quando um processo roda em primeiro plano, ele bloqueia a entrada do terminal até sua conclusão. Em segundo plano, o comando executa enquanto você pode continuar digitando novos comandos. Isso é controlado pelo operador <code>&</code>.</p>
<pre><code class="language-bash">#!/bin/bash
Processo em primeiro plano (bloqueante)
echo "Iniciando backup..."
tar -czf backup.tar.gz /home/usuario/dados
echo "Backup concluído"
Processo em segundo plano (não-bloqueante)
echo "Iniciando sincronização em background..."
rsync -av /local/ /remoto/ &
SYNC_PID=$!
echo "Sincronização rodando com PID: $SYNC_PID"
Continua executando sem esperar
echo "Script pode fazer outras tarefas..."
wait $SYNC_PID
echo "Sincronização finalizada"</code></pre>
<p>O comando <code>wait</code> pausa a execução do script até que o processo especificado termine. Se você usar <code>wait</code> sem argumentos, aguarda todos os processos em background.</p>
<h3>Monitorando e Controlando Processos</h3>
<p>Para criar scripts robustos, você precisa monitorar o status dos processos filhos. O comando <code>jobs</code> lista processos em background no contexto atual, enquanto <code>ps</code> fornece uma visão mais detalhada de todos os processos do sistema.</p>
<pre><code class="language-bash">#!/bin/bash
Iniciando múltiplos processos em background
for i in {1..3}; do
sleep 30 &
PIDS[$i]=$!
echo "Processo $i iniciado com PID: ${PIDS[$i]}"
done
Monitorando todos os processos
echo "Aguardando conclusão de todos os processos..."
for pid in "${PIDS[@]}"; do
if wait $pid; then
echo "Processo $pid finalizou com sucesso"
else
echo "Processo $pid retornou erro: $?"
fi
done
echo "Todos os processos concluídos"</code></pre>
<h2>Sinais: Comunicação entre Processos</h2>
<p>Sinais são mecanismos de comunicação interprocessual (IPC) que permitem ao sistema operacional notificar um processo sobre eventos. Existem dezenas de sinais em Unix/Linux, sendo alguns dos mais importantes: SIGTERM (encerramento graciosa), SIGKILL (encerramento forçado), SIGSTOP (pausar), SIGCONT (retomar) e SIGUSR1/SIGUSR2 (sinais personalizados definidos pelo usuário).</p>
<p>Quando você pressiona Ctrl+C no terminal, o sistema envia um sinal SIGINT (signal interrupt) ao processo. Ctrl+Z envia SIGSTOP. Diferentemente de SIGKILL, que não pode ser capturado, a maioria dos sinais pode ser tratada por um script através de handlers personalizados, permitindo limpeza adequada de recursos.</p>
<h3>Sinais Comuns em Scripts Shell</h3>
<p>Os sinais mais relevantes para scripting são SIGTERM, SIGINT, SIGHUP (hangup), SIGALRM (alarme), e os sinais do usuário. Cada sinal possui um número associado — SIGTERM é 15, SIGKILL é 9, SIGINT é 2. Você pode enviar sinais usando o comando <code>kill -SINAL PID</code>.</p>
<pre><code class="language-bash">#!/bin/bash
Enviando sinais para um processo
sleep 100 &
SLEEP_PID=$!
echo "Processo iniciado com PID: $SLEEP_PID"
sleep 2
echo "Enviando SIGTERM (encerramento graciosa)..."
kill -TERM $SLEEP_PID
Verifica se o processo ainda está rodando
sleep 1
if kill -0 $SLEEP_PID 2>/dev/null; then
echo "Processo ainda ativo, enviando SIGKILL..."
kill -KILL $SLEEP_PID
else
echo "Processo finalizou com SIGTERM"
fi
Aguarda a conclusão
wait $SLEEP_PID 2>/dev/null
echo "Status final: $?"</code></pre>
<p>A opção <code>-0</code> do comando <code>kill</code> testa se um processo existe sem enviar sinal algum — útil para verificar se um PID ainda está ativo.</p>
<h2>Trap: Capturando e Tratando Sinais</h2>
<p>O comando <code>trap</code> é o mecanismo principal para capturar sinais e executar ações customizadas. Quando um script recebe um sinal, ao invés de ser encerrado abruptamente, ele pode executar uma função ou comando definido no <code>trap</code>. Isso permite limpeza de arquivos temporários, fechamento de conexões, e outras operações críticas.</p>
<p>A sintaxe básica é <code>trap 'comando' SINAL</code>. Você pode definir múltiplos traps para diferentes sinais no mesmo script. O trap é herdado por processos filhos, mas pode ser redefinido ou removido com <code>trap - SINAL</code>.</p>
<h3>Implementando Limpeza Segura com Trap</h3>
<p>Um padrão essencial em scripting robusto é definir uma função de limpeza e associá-la aos sinais SIGTERM e SIGINT. Isso garante que recursos sejam liberados mesmo se o script for interrompido.</p>
<pre><code class="language-bash">#!/bin/bash
Variáveis globais
TEMP_DIR=$(mktemp -d)
LOCK_FILE="/tmp/meu_script.lock"
CHILD_PIDS=()
Função de limpeza
cleanup() {
local exit_code=$?
echo "Executando limpeza..."
Finaliza processos filhos
for pid in "${CHILD_PIDS[@]}"; do
if kill -0 $pid 2>/dev/null; then
echo "Finalizando processo filho: $pid"
kill -TERM $pid
Aguarda com timeout
local count=0
while kill -0 $pid 2>/dev/null && [ $count -lt 5 ]; do
sleep 1
((count++))
done
Força se ainda estiver ativo
if kill -0 $pid 2>/dev/null; then
kill -KILL $pid
fi
fi
done
Remove arquivos temporários
if [ -d "$TEMP_DIR" ]; then
rm -rf "$TEMP_DIR"
echo "Diretório temporário removido"
fi
Remove lock file
rm -f "$LOCK_FILE"
echo "Limpeza concluída"
exit $exit_code
}
Registra a função de limpeza
trap cleanup SIGTERM SIGINT EXIT
Verifica lock file para evitar múltiplas execuções
if [ -f "$LOCK_FILE" ]; then
echo "Outro processo já está em execução"
exit 1
fi
echo $$ > "$LOCK_FILE"
Executa trabalho principal
echo "Iniciando processamento..."
for i in {1..5}; do
(
echo "Tarefa $i em execução (PID: $$)"
sleep 10
echo "Tarefa $i concluída"
) &
CHILD_PIDS+=($!)
done
Aguarda todos os filhos
wait
echo "Processamento finalizado"</code></pre>
<p>Este exemplo demonstra um padrão robusto: define uma função <code>cleanup</code> que trata múltiplos aspectos (finalização de filhos com timeout, remoção de temporários, lock file), registra essa função para SIGTERM, SIGINT e EXIT. O sinal EXIT é especial — dispara ao final normal do script também, garantindo limpeza em todos os cenários.</p>
<h3>Tratando Sinais Específicos Diferentemente</h3>
<p>Às vezes você quer comportamentos diferentes para sinais distintos. Por exemplo, SIGTERM pode ser uma "solicitação educada", enquanto SIGINT (Ctrl+C) deveria ser imediato.</p>
<pre><code class="language-bash">#!/bin/bash
Estado global
GRACEFUL_SHUTDOWN=false
Processa SIGTERM com encerramento graciosa
on_sigterm() {
echo "SIGTERM recebido - iniciando encerramento graciosa"
GRACEFUL_SHUTDOWN=true
}
Processa SIGINT com encerramento imediata
on_sigint() {
echo "SIGINT recebido - encerrando imediatamente"
exit 130 # Código padrão para SIGINT
}
trap on_sigterm SIGTERM
trap on_sigint SIGINT
echo "Processando 100 itens..."
for i in {1..100}; do
if [ "$GRACEFUL_SHUTDOWN" = true ]; then
echo "Encerramento graciosa: parando após item $i"
break
fi
echo "Processando item $i"
sleep 1
done
echo "Script finalizado normalmente"</code></pre>
<h2>Scripts Robustos: Padrões e Boas Práticas</h2>
<p>Um script robusto não é apenas um que funciona — é aquele que falha de forma previsível, fornece feedback adequado, lida com erros e se recupera quando possível. Robustez envolve tratamento de erros, validação de entrada, logging, e tratamento de sinais que já discutimos. Vamos integrar todos esses conceitos em um exemplo real.</p>
<h3>Estrutura Recomendada para Scripts Produção</h3>
<p>Scripts destinados a ambiente de produção devem seguir um padrão consistente. Começa com shebang e declaração de opções do shell, prossegue para configuração e funções, depois lógica principal. Cada função deve ter responsabilidade clara e o script deve sair com código apropriado.</p>
<pre><code class="language-bash">#!/bin/bash
set -euo pipefail # Exit on error, undefined vars, pipe failures
IFS=$'\n\t' # Safer field separator
Configuração
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_NAME="$(basename "$0")"
readonly LOG_FILE="${LOG_FILE:-/var/log/${SCRIPT_NAME}.log}"
readonly LOCK_FILE="/tmp/${SCRIPT_NAME}.lock"
readonly PID_FILE="/var/run/${SCRIPT_NAME}.pid"
Variáveis de estado
declare -a CHILD_PIDS=()
CURRENT_TASK=""
Funções de logging
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] $*" | tee -a "$LOG_FILE"
}
error() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] $*" | tee -a "$LOG_FILE" >&2
}
Função de limpeza
cleanup() {
local exit_code=$?
if [ -n "$CURRENT_TASK" ]; then
error "Interrompido durante: $CURRENT_TASK"
fi
Finaliza filhos
for pid in "${CHILD_PIDS[@]}"; do
if kill -0 "$pid" 2>/dev/null; then
log "Finalizando processo $pid"
if ! kill -TERM "$pid" 2>/dev/null; then
kill -KILL "$pid" 2>/dev/null || true
fi
fi
done
wait "${CHILD_PIDS[@]}" 2>/dev/null || true
Remove lock
rm -f "$LOCK_FILE" "$PID_FILE"
log "Script finalizado com código: $exit_code"
exit "$exit_code"
}
Handlers de sinal
on_sigterm() {
log "SIGTERM recebido"
cleanup
}
on_sigint() {
log "SIGINT recebido (Ctrl+C)"
cleanup
}
Validação de pré-requisitos
check_requirements() {
local required_cmds=("curl" "jq" "openssl")
for cmd in "${required_cmds[@]}"; do
if ! command -v "$cmd" &> /dev/null; then
error "Comando obrigatório não encontrado: $cmd"
exit 1
fi
done
if [ ! -w "$(dirname "$LOG_FILE")" ]; then
error "Sem permissão de escrita em $(dirname "$LOG_FILE")"
exit 1
fi
}
Verificação de lock
acquire_lock() {
if [ -f "$LOCK_FILE" ]; then
local old_pid
old_pid=$(cat "$LOCK_FILE" 2>/dev/null || echo "")
if [ -n "$old_pid" ] && kill -0 "$old_pid" 2>/dev/null; then
error "Outro processo rodando (PID: $old_pid)"
exit 1
fi
rm -f "$LOCK_FILE"
fi
echo $$ > "$LOCK_FILE"
echo $$ > "$PID_FILE"
}
Funções de negócio
process_data() {
local input_file="$1"
CURRENT_TASK="processar dados de $input_file"
log "Iniciando: $CURRENT_TASK"
Validação
if [ ! -f "$input_file" ]; then
error "Arquivo não encontrado: $input_file"
return 1
fi
if [ ! -r "$input_file" ]; then
error "Sem permissão de leitura: $input_file"
return 1
fi
Processamento
local line_count=0
while IFS= read -r line; do
((line_count++))
Simula processamento
sleep 0.1
if [ $((line_count % 10)) -eq 0 ]; then
log "Processadas $line_count linhas"
fi
done < "$input_file"
log "Processamento concluído: $line_count linhas"
return 0
}
fetch_remote_data() {
local url="$1"
local output_file="$2"
CURRENT_TASK="baixar dados de $url"
log "Iniciando: $CURRENT_TASK"
if ! curl -sSf -m 30 -o "$output_file" "$url"; then
error "Falha ao baixar de $url"
return 1
fi
log "Download concluído: $output_file"
return 0
}
Programa principal
main() {
log "Iniciando $SCRIPT_NAME (PID: $$)"
check_requirements
acquire_lock
Registra handlers
trap on_sigterm SIGTERM
trap on_sigint SIGINT
trap cleanup EXIT
Trabalho em paralelo
log "Iniciando processamento paralelo"
fetch_remote_data "https://example.com/data.json" "/tmp/data.json" &
CHILD_PIDS+=($!)
process_data "/etc/hostname" &
CHILD_PIDS+=($!)
Aguarda conclusão
local failed=0
for pid in "${CHILD_PIDS[@]}"; do
if ! wait "$pid"; then
((failed++))
fi
done
CURRENT_TASK=""
if [ $failed -gt 0 ]; then
error "$failed tarefas falharam"
return 1
fi
log "Todas as tarefas completadas com sucesso"
return 0
}
Executa
main "$@"</code></pre>
<p>Este exemplo consolida tudo que vimos: usa <code>set -euo pipefail</code> para falhar rápido e seguro, implementa logging estruturado, trata sinais adequadamente, valida pré-requisitos, gerencia lock files, processa múltiplas tarefas em paralelo, e fornece limpeza garantida.</p>
<h2>Conclusão</h2>
<p>Dominar shell scripting avançado exige compreensão profunda de três pilares: <strong>processos e seu ciclo de vida</strong> — você agora sabe como iniciar, monitorar e finalizar processos corretamente, utilizando <code>&</code>, <code>wait</code> e <code>jobs</code> para coordenação; <strong>sinais como mecanismo de comunicação</strong> — compreende que sinais como SIGTERM, SIGINT e SIGKILL são notificações que permitem ao sistema se comunicar com processos, e que a maioria pode ser capturada para ações customizadas; <strong>trap para tratamento robusto</strong> — implementa handlers que garantem limpeza de recursos mesmo em cenários de interrupção, transformando scripts frágeis em soluções confiáveis para produção.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://www.gnu.org/software/bash/manual/html_node/Signals.html" target="_blank" rel="noopener noreferrer">GNU Bash Manual - Signals</a></li>
<li><a href="https://man7.org/linux/man-pages/man7/signal.7.html" target="_blank" rel="noopener noreferrer">Linux man-pages: signal(7)</a></li>
<li><a href="https://man7.org/linux/man-pages/man1/trap.1p.html" target="_blank" rel="noopener noreferrer">Linux man-pages: trap(1p)</a></li>
<li><a href="https://www.tldp.org/LDP/abs/html/index.html" target="_blank" rel="noopener noreferrer">Advanced Bash-Scripting Guide - Signals and Traps</a></li>
<li><a href="https://linuxcommand.org/" target="_blank" rel="noopener noreferrer">The Linux Command Line by William Shotts - Chapter on Job Control</a></li>
</ul>
<p><!-- FIM --></p>