Como usar sinais Linux em scripts Bash

Laptop Linux mostrando um prompt do bash

O kernel do Linux envia sinais aos processos sobre eventos aos quais eles precisam reagir. Scripts bem comportados lidam com sinais de maneira elegante e robusta e podem se limpar mesmo se você pressionar Ctrl+C. Veja como.

Sinais e Processos

Sinais são mensagens curtas, rápidas e unidirecionais enviadas para processos como scripts, programas e daemons. Eles informam ao processo sobre algo que aconteceu. O usuário pode ter pressionado Ctrl+C ou o aplicativo pode ter tentado gravar na memória à qual não tem acesso.

Se o autor do processo previu que um determinado sinal poderia ser enviado a ele, ele poderá escrever uma rotina no programa ou script para lidar com esse sinal. Essa rotina é chamada de manipulador de sinal. Ele captura ou prende o sinal e executa alguma ação em resposta a ele.

O Linux usa muitos sinais, como veremos, mas do ponto de vista do script, há apenas um pequeno subconjunto de sinais nos quais você provavelmente estará interessado. Em particular, em scripts não triviais, sinais que informam ao O script para desligamento deve ser interceptado (quando possível) e um desligamento normal executado.

Por exemplo, scripts que criam arquivos temporários ou abrem portas de firewall podem ter a oportunidade de excluir os arquivos temporários ou fechar as portas antes que elas sejam encerradas. Se o script morrer no instante em que receber o sinal, seu computador poderá ficar em um estado imprevisível.

Veja como você pode lidar com sinais em seus próprios scripts.

Conheça os sinais

Alguns comandos do Linux têm nomes enigmáticos. Não é assim com o comando que captura sinais. Chama-se trap. Também podemos usar trapcom o

 -l 

(lista) opção para nos mostrar a lista completa de  sinais que o Linux usa .

armadilha -l

Listando os sinais no Ubuntu com trap -l

Embora nossa lista numerada termine em 64, na verdade existem 62 sinais. Os sinais 32 e 33 estão faltando. Eles  não são implementados no Linux . Eles foram substituídos pela funcionalidade do gcccompilador para lidar com threads em tempo real. Tudo, desde o sinal 34, SIGRTMINaté o sinal 64, SIGRTMAXsão sinais em tempo real.

Você verá listas diferentes em diferentes sistemas operacionais do tipo Unix. No OpenIndiana , por exemplo, os sinais 32 e 33 estão presentes, juntamente com vários sinais extras, elevando a contagem total para 73.

Listando os sinais no OpenIndiana com trap -l

Os sinais podem ser referenciados por nome, número ou pelo nome abreviado. Seu nome abreviado é simplesmente o nome com o “SIG” inicial removido.

Os sinais são gerados por diversos motivos. Se você conseguir decifrá-los, seu propósito está contido em seu nome. O impacto de um sinal se enquadra em uma das seguintes categorias:

  • Terminar:  O processo é encerrado .
  • Ignorar:  O sinal não afeta o processo. Este é um sinal apenas informativo.
  • Núcleo:  Um arquivo dump-core é criado. Isso geralmente é feito porque o processo transgrediu de alguma forma, como uma violação de memória.
  • Parar:  O processo é interrompido. Ou seja, está  pausado , não encerrado.
  • Continuar:  informa a um processo interrompido para continuar a execução.
Recomendado:  Como desbloquear seu Chromebook com um PIN

Estes são os sinais que você encontrará com mais frequência.

  • SIGHUP : Sinal 1. A conexão com um host remoto — como um servidor SSH — caiu inesperadamente ou o usuário efetuou logout. Um script que recebe esse sinal pode terminar normalmente ou pode optar por tentar se reconectar ao host remoto.
  • SIGINT : Sinal 2. O usuário pressionou a combinação Ctrl + C para forçar o fechamento de um processo, ou o killcomando foi usado com o sinal 2. Tecnicamente, este é um sinal de interrupção, não um sinal de encerramento, mas um script interrompido sem um o manipulador de sinal geralmente será encerrado.
  • SIGQUIT : Sinal 3. O usuário pressionou a combinação Ctrl+D para forçar o encerramento de um processo ou o killcomando foi usado com o sinal 3.
  • SIGFPE : Sinal 8. O processo tentou realizar uma operação matemática ilegal (impossível), como divisão por zero.
  • SIGKILL : Sinal 9. Este é o sinal equivalente a uma guilhotina. Você não pode captá-lo ou ignorá-lo, e isso acontece instantaneamente. O processo é encerrado imediatamente.
  • SIGTERM : Sinal 15. Esta é a versão mais atenciosa do SIGKILL. SIGTERM também diz a um processo para encerrar, mas ele pode ficar preso e o processo pode executar seus processos de limpeza antes de encerrar. Isso permite um desligamento normal. Este é o sinal padrão gerado pelo killcomando.

Sinais na linha de comando

Uma maneira de capturar um sinal é usar trapo número ou nome do sinal e uma resposta que você deseja que aconteça se o sinal for recebido. Podemos demonstrar isso em uma janela de terminal.

Este comando captura o SIGINTsinal. A resposta é imprimir uma linha de texto na janela do terminal. Estamos usando a -eopção (enable escapes) echopara que possamos usar o \nespecificador de formato ” “.

trap 'echo -e "\nCtrl+c detectado."' SIGINT

Trapping Ctrl+C na linha de comando

Nossa linha de texto é impressa cada vez que pressionamos a combinação Ctrl+C.

Para ver se uma interceptação está definida em um sinal, use a -popção (print trap).

armadilha -p SIGINT

Verificando se uma armadilha está definida em um sinal

Usar trapsem opções faz a mesma coisa.

Para redefinir o sinal para seu estado normal e não capturado, use um hífen ” -” e o nome do sinal capturado.

armadilha - SIGINT

armadilha -p SIGINT

Removendo uma armadilha de um sinal

Nenhuma saída do trap -pcomando indica que não há nenhuma armadilha definida nesse sinal.

Capturando Sinais em Scripts

Podemos usar o mesmo trapcomando de formato geral dentro de um script. Este script captura três sinais diferentes, SIGINT, SIGQUITe SIGTERM.

#!/bin/bash

trap "echo I foi encerrado com SIGINT; exit" SIGINT

trap "echo I was SIGQUIT encerrado; exit" SIGQUIT

trap "echo I was SIGTERM encerrado; exit" SIGTERM

eco $$

contador=0

enquanto verdadeiro

fazer

  echo "Número do loop:" $((++contador))

  dormir 1

feito

As três trapdeclarações estão no topo do script. Observe que incluímos o exitcomando na resposta a cada um dos sinais. Isso significa que o script reage ao sinal e sai.

Recomendado:  WhatsApp não funciona? 9 dicas para solução de problemas

Copie o texto em seu editor e salve-o em um arquivo chamado “simple-loop.sh”, e torne-o executável usando o chmodcomando . Você precisará fazer isso com todos os scripts deste artigo se quiser acompanhar em seu próprio computador. Basta usar o nome do script apropriado em cada caso.

chmod +x loop simples.sh

Tornando um script executável com chmod

O resto do script é muito simples. Precisamos saber o ID do processo do script, para que o script repita isso para nós. A $$variável contém o ID do processo do script.

Criamos uma variável chamada counter e a definimos como zero.

O whileloop será executado para sempre, a menos que seja interrompido à força. Ele incrementa a countervariável, faz eco na tela e dorme por um segundo.

Vamos executar o script e enviar sinais diferentes para ele.

./loop simples.sh

Um script que o identifica foi finalizado com Ctrl+C

Quando pressionamos “Ctrl+C” nossa mensagem é impressa na janela do terminal e o script é finalizado.

Vamos executá-lo novamente e enviar o SIGQUITsinal usando o killcomando. Precisaremos fazer isso em outra janela do terminal. Você precisará usar o ID do processo relatado pelo seu próprio script.

./loop simples.sh

matar -SIGQUIT 4575

Um script que o identifica foi encerrado com SIGQUIT

Como esperado, o script relata que o sinal chega e termina. E finalmente, para provar isso, faremos novamente com o SIGTERMsinal.

./loop simples.sh

matar -SIGTERM 4584

Um script que o identifica foi encerrado com SIGTERM

Verificamos que podemos capturar vários sinais em um script e reagir a cada um deles de forma independente. A etapa que transforma tudo isso de interessante em útil é adicionar manipuladores de sinal.

Tratamento de sinais em scripts

Podemos substituir a string de resposta pelo nome de uma função em seu script. O trapcomando então chama essa função quando o sinal é detectado.

Copie este texto em um editor e salve-o como um arquivo chamado “grace.sh”, e torne-o executável com a extensão chmod.

#!/bin/bash

armadilha Graceful_shutdown SIGINT SIGQUIT SIGTERM

Graceful_shutdown()

{

  echo -e "\nRemovendo arquivo temporário:" $temp_file

  rm -rf "$ arquivo_temp"

  saída

}

arquivo_temp=$(mktemp -p /tmp tmp.XXXXXXXXXX)

echo "Arquivo temporário criado:" $temp_file

contador=0

enquanto verdadeiro

fazer

  echo "Número do loop:" $((++contador))

  dormir 1

feito

O script define uma armadilha para três sinais diferentes — SIGHUP, SIGINTe SIGTERM— usando uma única trapinstrução. A resposta é o nome da graceful_shutdown()função. A função é chamada sempre que um dos três sinais capturados é recebido.

O script cria um arquivo temporário no diretório “/tmp”, usando mktemp. O modelo de nome de arquivo é “tmp.XXXXXXXXXX”, portanto o nome do arquivo será “tmp”. seguido por dez caracteres alfanuméricos aleatórios. O nome do arquivo é exibido na tela.

O resto do script é igual ao anterior, com uma countervariável e um whileloop infinito.

./graça.sh

Um script executando um desligamento normal excluindo um arquivo temporário

Quando o arquivo recebe um sinal que faz com que ele feche, a graceful_shutdown()função é chamada. Isso exclui nosso único arquivo temporário. Em uma situação do mundo real, ele poderia realizar qualquer limpeza que seu script exigisse.

Além disso, agrupamos todos os nossos sinais capturados e os tratamos com uma única função. Você pode capturar sinais individualmente e enviá-los para suas próprias funções de manipulador dedicadas.

Recomendado:  O que é “processo ocioso do sistema” e por que ele está usando tanta CPU?

Copie este texto e salve-o em um arquivo chamado “triple.sh”, e torne-o executável usando o chmod comando.

#!/bin/bash

armadilha sigint_handler SIGINT

armadilha sigusr1_handler SIGUSR1

armadilha exit_handler SAIR

função sigint_handler() {

  ((++sigint_count))

  echo -e "\nSIGINT recebeu $sigint_count tempo(s)."

  if [[ "$sigint_count" -eq 3 ]]; então

    echo "Iniciando fechamento."

    loop_flag = 1

  fi

}

função sigusr1_handler() {

  echo "SIGUSR1 enviou e recebeu $((++sigusr1_count)) tempo(s)."

}

função exit_handler() {

  echo "Manipulador de saída: o script está fechando..."

}

eco $$

sigusr1_count=0

sigint_count=0

loop_flag=0

enquanto [[ $loop_flag -eq 0 ]]; fazer

  matar -SIGUSR1 $$

  dormir 1

feito

Definimos três armadilhas no topo do script.

  • Um deles captura SIGINT e tem um manipulador chamado sigint_handler().
  • O segundo captura um sinal chamado SIGUSR1e usa um manipulador chamado sigusr1_handler().
  • A armadilha número três captura o EXITsinal. Este sinal é gerado pelo próprio script quando ele é fechado. Definir um manipulador de sinal EXITsignifica que você pode definir uma função que sempre será chamada quando o script terminar (a menos que seja eliminado com signal SIGKILL). Nosso manipulador é chamado exit_handler().

SIGUSR1e SIGUSR2são sinais fornecidos para que você possa enviar sinais personalizados para seus scripts. Como você interpreta e reage a eles depende inteiramente de você.

Deixando os manipuladores de sinais de lado por enquanto, o corpo do script deve ser familiar para você. Ele ecoa o ID do processo na janela do terminal e cria algumas variáveis. A variável sigusr1_countregistra o número de vezes SIGUSR1que foi manipulado e sigint_countregistra o número de vezes SIGINTque foi manipulado. A loop_flagvariável é definida como zero.

O whileloop não é um loop infinito. Ele irá parar o loop se a loop_flagvariável for definida com qualquer valor diferente de zero. Cada rotação do whileloop killenvia o SIGUSR1sinal para este script, enviando-o para o ID do processo do script. Os scripts podem enviar sinais para si mesmos!

A sigusr1_handler()função incrementa a sigusr1_countvariável e envia uma mensagem para a janela do terminal.

Cada vez que o SIGINTsinal é recebido, a siguint_handler()função incrementa a sigint_countvariável e ecoa seu valor na janela do terminal.

Se a sigint_countvariável for igual a três, a loop_flagvariável é definida como um e uma mensagem é enviada para a janela do terminal informando ao usuário que o processo de desligamento foi iniciado.

Como loop_flagnão é mais igual a zero, o whileloop termina e o script é finalizado. Mas essa ação aumenta automaticamente o EXITsinal e a exit_handler()função é chamada.

./triplo.sh

Um script usando SIGUSR1, exigindo três combinações Ctrl+C para fechar e capturando o sinal EXIT no desligamento

Após três pressionamentos de Ctrl+C, o script termina e invoca automaticamente a exit_handler()função.

Leia os sinais

Ao capturar sinais e lidar com eles em funções de manipulador simples, você pode fazer com que seus scripts Bash sejam organizados, mesmo que sejam encerrados inesperadamente. Isso fornece um sistema de arquivos mais limpo. Ele também evita a instabilidade na próxima vez que você executar o script e – dependendo da finalidade do seu script – pode até evitar falhas de segurança .