Cómo propagar una señal a los procesos infantiles de un script bash

Cómo propagar una señal a los procesos infantiles de un script bash

Supongamos que escribimos un script que genera uno o más procesos de ejecución larga; Si dicho script recibe una señal como Firme o Siglo, Probablemente queramos que sus hijos también sean terminados (normalmente cuando los padres mueren, los niños sobreviven). Es posible que también queramos realizar algunas tareas de limpieza antes de que salga el script en sí mismo. Para poder alcanzar nuestro objetivo, primero debemos aprender sobre los grupos de procesos y cómo ejecutar un proceso en segundo plano.

En este tutorial aprenderás:

  • ¿Qué es un grupo de procesos?
  • La diferencia entre los procesos de primer plano y fondo
  • Cómo ejecutar un programa en segundo plano
  • Cómo usar el caparazón esperar integrado para esperar un proceso ejecutado en segundo plano
  • Cómo terminar los procesos infantiles cuando el padre recibe una señal
Cómo propagar una señal a los procesos infantiles de un script bash

Requisitos y convenciones de software utilizados

Requisitos de software y convenciones de línea de comandos de Linux
Categoría Requisitos, convenciones o versión de software utilizada
Sistema Distribución independiente
Software No se necesita software específico
Otro Ninguno
Convenciones # - requiere que los comandos de Linux dados se ejecuten con privilegios raíz directamente como un usuario raíz o mediante el uso de sudo dominio
ps - Requiere que los comandos de Linux dados se ejecuten como un usuario regular no privilegiado

Un ejemplo simple

Creemos un script muy simple y simulemos el lanzamiento de un proceso de larga ejecución:

#!/bin/bash trampa "se recibió la señal de eco!"Sigint Echo" El guión pid es $ "Sleep 30 
Copiar

Lo primero que hicimos en el guión fue crear una trampa para atrapar Firme e imprima un mensaje cuando se reciba la señal. Que hicimos que nuestro script imprima su pid: Podemos obtener al expandir el $$ variable. A continuación, ejecutamos el dormir comandar simular un proceso de ejecución largo (30 segundos).

Guardamos el código dentro de un archivo (digamos que se llama prueba.mierda), hacerlo ejecutable y lanzarlo desde un emulador terminal. Obtenemos el siguiente resultado:

El script pid es 101248 

Si estamos enfocados en el emulador terminal y presione Ctrl+C mientras el script se está ejecutando, un Firme la señal es enviada y manejada por nuestro trampa:

El script pid es 101248 ^Csignal recibido! 

Aunque la trampa manejó la señal como se esperaba, el script fue interrumpido de todos modos. Por que esto pasó? Además, si enviamos un Firme señal al script utilizando el matar Comando, el resultado que obtenemos es bastante diferente: la trampa no se ejecuta de inmediato y el script continúa hasta que el proceso infantil no salga (después 30 segundos de "dormir"). Por qué esta diferencia? Vamos a ver…

Grupos de procesos, empleos en primer plano y antecedentes

Antes de responder las preguntas anteriores, debemos comprender mejor el concepto de grupo de procesos.

Un grupo de procesos es un grupo de procesos que comparten el mismo pgid (ID de grupo de procesos). Cuando un miembro de un grupo de proceso crea un proceso infantil, ese proceso se convierte en miembro del mismo grupo de proceso. Cada grupo de proceso tiene un líder; Podemos reconocerlo fácilmente porque es pid y el pgid son lo mismo.

Podemos visualizar pid y pgid de ejecutar procesos utilizando el PD dominio. La salida del comando se puede personalizar para que solo se muestren los campos que nos interesan: en este caso CMD, Pid y Pgid. Hacemos esto usando el -O Opción, proporcionando una lista de campos separada por comas como argumento:

$ ps -a -o pid, pgid, cmd 

Si ejecutamos el comando mientras nuestro script está ejecutando la parte relevante de la salida que obtenemos es la siguiente:

 PID PGID CMD 298349 298349 /Bin /Bash ./prueba.SH 298350 298349 Dormir 30 

Podemos ver claramente dos procesos: el pid del primero es 298349, Igual que su pgid: Este es el líder del grupo de procesos. Fue creado cuando lanzamos el script como puede ver en el CMD columna.

Este proceso principal lanzó un proceso infantil con el comando Dormir 30: Como se esperaba, los dos procesos están en el mismo grupo de procesos.

Cuando presionamos CTRL-C mientras nos enfocamos en el terminal desde el cual se lanzó el script, la señal no se envió solo al proceso principal, sino a todo el grupo de proceso. Que grupo de procesos? El Grupo de procesos de primer plano de la terminal. Se llama a todos los procesos miembros de este grupo procesos en primer plano, Todos los demás se llaman procesos de fondo. Esto es lo que el manual de Bash tiene que decir al respecto:

SABÍAS?Para facilitar la implementación de la interfaz de usuario al control de trabajo, el sistema operativo mantiene la noción de una identificación de grupo de proceso terminal actual. Miembros de este grupo de proceso (procesos cuya ID de grupo de proceso es igual a la ID de grupo de proceso terminal actual) Reciba señales generadas por teclado como Sigint. Se dice que estos procesos están en primer plano. Los procesos de fondo son aquellos cuya identificación de grupo de proceso difiere del terminal; Dichos procesos son inmunes a las señales generadas por el teclado.

Cuando enviamos el Firme señal con el matar Comando, en cambio, solo nos dirigimos al PID del proceso principal; Bash exhibe un comportamiento específico cuando se recibe una señal mientras espera que se complete un programa: el "código de trampa" para esa señal no se ejecuta hasta que ese proceso haya terminado. Es por eso que el mensaje "Señal recibido" se mostró solo después del dormir comando salido.

Para replicar lo que sucede cuando presionamos Ctrl-C en el terminal usando el matar Comando para enviar la señal, debemos apuntar al grupo de proceso. Podemos enviar una señal a un grupo de procesos utilizando La negación del PID del líder del proceso, Entonces, suponiendo el pid del líder del proceso es 298349 (Como en el ejemplo anterior), ejecutaríamos:

$ Kill -2 -298349 

Administrar la propagación de la señal desde el interior de un script

Ahora, supongamos que lanzamos un script de ejecución larga desde un shell no interactivo, y queremos que dicho script administre la propagación de la señal automáticamente, de modo que cuando reciba una señal como Firme o Siglo Termina a su niño potencialmente largo, y finalmente realiza algunas tareas de limpieza antes de salir. Cómo podemos hacer esto?

Como lo hicimos anteriormente, podemos manejar la situación en la que se recibe una señal en una trampa; Sin embargo, como vimos, si se recibe una señal mientras el shell está esperando que se complete un programa, el "código de trampa" se ejecuta solo después de que salga el proceso infantil.

Esto no es lo que queremos: queremos que el código de trampa se procese tan pronto como el proceso principal reciba la señal. Para lograr nuestro objetivo, debemos ejecutar el proceso infantil en el fondo: Podemos hacer esto colocando el Y símbolo después del comando. En nuestro caso escribiríamos:

#!/Bin/Bash Trap 'Echo Signal recibida!'Sigint Echo "El script pid es $" dormir 30 y 
Copiar

Si dejaríamos el script de esta manera, el proceso principal saldría justo después de la ejecución del Dormir 30 comando, dejándonos sin la oportunidad de realizar tareas de limpieza después de que termine o se interrumpe. Podemos resolver este problema usando el shell esperar incorporado. La página de ayuda de esperar lo define de esta manera:



Espera cada proceso identificado por una identificación, que puede ser una identificación de proceso o una especificación de trabajo, e informa su estado de terminación. Si no se da ID, espera todos los procesos infantiles activos actualmente, y el estado de retorno es cero.

Después de establecer un proceso para ser ejecutado en segundo plano, podemos recuperar su pid en el ps! variable. Podemos pasarlo como argumento para esperar Para hacer que el proceso de los padres espere a su hijo:

#!/Bin/Bash Trap 'Echo Signal recibida!'Sigint echo "El guión pid es $" Sleep 30 y espera $! 
Copiar

Terminamos? No, todavía hay un problema: la recepción de una señal manejada en una trampa dentro del guión, causa el esperar construido para regresar de inmediato, sin esperar la terminación del comando en segundo plano. Este comportamiento está documentado en el manual de bash:

Cuando Bash está esperando un comando asincrónico a través de la espera construida, la recepción de una señal para la cual se ha establecido una trampa hará que la espera construya inmediatamente con un estado de salida superior a 128, inmediatamente después de lo cual se ejecuta la trampa. Esto es bueno, porque la señal se maneja de inmediato y la trampa se ejecuta sin tener que esperar a que el niño termine, pero plantea un problema, ya que en nuestra trampa queremos ejecutar nuestras tareas de limpieza solo una vez que estamos seguros de que el niño Proceso salido.

Para resolver este problema tenemos que usar esperar de nuevo, tal vez como parte de la trampa en sí. Así es como nuestro guión podría verse al final:

#!/bin/bash cleanup () echo "limpieza ..." # Nuestro código de limpieza va aquí trampa 'Echo Signal recibida!; matar "$ child_pid"; esperar "$ child_pid"; Cleanup 'Sigint Sigterter Echo "El script pid es $" sleep 30 & child_pid = "$!"espera" $ child_pid " 
Copiar

En el guión creamos un limpiar función donde pudimos insertar nuestro código de limpieza e hicieron nuestro trampa atrapar también el Siglo señal. Esto es lo que sucede cuando ejecutamos este script y le enviamos una de esas dos señales:

  1. Se inicia el script y el Dormir 30 El comando se ejecuta en el fondo;
  2. El pid del proceso infantil está "almacenado" en el Child_pid variable;
  3. El script espera la terminación del proceso infantil;
  4. El guión recibe un Firme o Siglo señal
  5. El esperar El comando regresa inmediatamente, sin esperar la terminación del niño;

En este punto se ejecuta la trampa. En eso:

  1. A Siglo señal (el matar predeterminado) se envía al Child_pid;
  2. Nosotros esperar Para asegurarse de que el niño termine después de recibir esta señal.
  3. Después esperar Devuelve, ejecutamos el limpiar función.

Propagar la señal a varios niños

En el ejemplo anterior trabajamos con un script que tenía un solo proceso de hijo. ¿Qué pasa si un guión tiene muchos hijos, y qué pasa si algunos de ellos tienen hijos propios??

En el primer caso, una forma rápida de obtener el pids de todos los niños es usar el Jobs -P Comando: este comando muestra los PID de todos los trabajos activos en el shell actual. Podemos que usar matar para terminarlos. Aquí hay un ejemplo:

#!/bin/bash cleanup () echo "limpieza ..." # Nuestro código de limpieza va aquí trampa 'Echo Signal recibida!; matar $ (trabajos -p); esperar; Cleanup 'Sigint Sigterter Echo "El script pid es $" Sleep 30 & Sleep 40 y espera 
Copiar

El script lanza dos procesos en segundo plano: mediante el uso del esperar incorporado sin argumentos, esperamos a todos y mantenemos vivo el proceso principal. Cuando el Firme o Siglo Las señales son recibidas por el guión, enviamos un Siglo a los dos, con sus pids devueltos por el Jobs -P dominio (trabajo es en sí mismo un caparazón incorporado, por lo que cuando lo usamos, no se crea un nuevo proceso).

Si los niños tienen su propio proceso de hijos, y queremos cancelarlos a todos cuando el antepasado reciba una señal, podemos enviar una señal a todo el grupo de proceso, como vimos antes.

Esto, sin embargo, presenta un problema, ya que al enviar una señal de terminación al grupo de procesos, ingresaríamos un bucle "Sign-Sent/Traped" Traped ". Piénselo: en el trampa para Siglo Enviamos un Siglo señal a todos los miembros del grupo de procesos; Esto incluye el script principal en sí mismo!

Para resolver este problema y aún poder ejecutar una función de limpieza después de que se terminen los procesos infantiles, debemos cambiar el trampa para Siglo Justo antes de enviar la señal al grupo de proceso, por ejemplo:

#!/bin/bash cleanup () echo "limpieza ..." # Nuestro código de limpieza va aquí trampa 'trampa "" Sigterm; matar 0; esperar; Cleanup 'Sigint Sigterter Echo "El script pid es $" Sleep 30 & Sleep 40 y espera 
Copiar

En la trampa, antes de enviar Siglo al grupo de procesos, cambiamos el Siglo trampa, de modo que el proceso principal ignora la señal y solo sus descendientes se ven afectados por ella. Observe también que en la trampa, para indicar el grupo de proceso, utilizamos matar con 0 como pid. Esta es una especie de atajo: cuando el pid pasó a matar es 0, todos los procesos en el actual Se señalan el grupo de procesos.

Conclusiones

En este tutorial aprendimos sobre los grupos de procesos y cuál es la diferencia entre los procesos de primer plano y los antecedentes. Aprendimos que Ctrl-C envía un Firme Señal a todo el grupo de proceso de primer plano del terminal de control, y aprendimos cómo enviar una señal a un grupo de procesos utilizando matar. También aprendimos a ejecutar un programa en segundo plano y cómo usar el esperar Shell incorporado para esperar a que salga sin perder el caparazón de los padres. Finalmente, vimos cómo configurar un script para que cuando reciba una señal termine a sus hijos antes de salir. Me he perdido algo? ¿Tiene sus recetas personales para realizar la tarea?? No dudes en hacerme saber!

Tutoriales de Linux relacionados:

  • Gestión de procesos de fondo bash
  • Scripting de bash y gestión de procesos multiprocesos en el ..
  • Una introducción a la automatización, herramientas y técnicas de Linux
  • Cómo ejecutar el comando en el fondo en Linux
  • Mastering Bash Script Loops
  • Cómo instalar la señal en Linux
  • Cómo rastrear las llamadas del sistema realizadas por un proceso con Strace On ..
  • Mint 20: Mejor que Ubuntu y Microsoft Windows?
  • Comparación de Linux Apache Prefork vs Worker MPMS
  • Cómo trabajar con grupos de paquetes DNF