GNU/Linux >> Tutoriales Linux >  >> Linux

¿No se puede detener un script Bash con Ctrl+c?

Escribí un script bash simple con un bucle para imprimir la fecha y hacer ping a una máquina remota:

#!/bin/bash
while true; do
    #     *** DATE: Thu Sep 17 10:17:50 CEST 2015  ***
    echo -e "\n*** DATE:" `date` " ***";
    echo "********************************************"
    ping -c5 $1;
done

Cuando lo ejecuto desde una terminal, no puedo detenerlo con Ctrl+C .
Parece que envía el ^C a la terminal, pero el script no se detiene.

MacAir:~ tomas$ ping-tester.bash www.google.com

*** DATE: Thu Sep 17 23:58:42 CEST 2015  ***
********************************************
PING www.google.com (216.58.211.228): 56 data bytes
64 bytes from 216.58.211.228: icmp_seq=0 ttl=55 time=39.195 ms
64 bytes from 216.58.211.228: icmp_seq=1 ttl=55 time=37.759 ms
^C                                                          <= That is Ctrl+C press
--- www.google.com ping statistics ---
2 packets transmitted, 2 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 40.887/59.699/78.510/18.812 ms

*** DATE: Thu Sep 17 23:58:48 CEST 2015  ***
********************************************
PING www.google.com (216.58.211.196): 56 data bytes
64 bytes from 216.58.211.196: icmp_seq=0 ttl=55 time=37.460 ms
64 bytes from 216.58.211.196: icmp_seq=1 ttl=55 time=37.371 ms

No importa cuántas veces lo presione o qué tan rápido lo haga. No soy capaz de detenerlo.
Haz la prueba y date cuenta por ti mismo.

Como solución secundaria, lo detendré con Ctrl+Z , eso lo detiene y luego kill %1 .

¿Qué está pasando exactamente aquí con ^C? ?

Respuesta aceptada:

Lo que pasa es que tanto bash y ping recibir el SIGINT (bash ser no interactivo, ambos ping y bash ejecutar en el mismo grupo de procesos que ha sido creado y configurado como el grupo de procesos de primer plano de la terminal por el shell interactivo desde el que ejecutó ese script).

Sin embargo, bash maneja ese SIGINT de forma asincrónica, solo después de que el comando que se está ejecutando actualmente haya salido. bash solo sale al recibir ese SIGINT si el comando que se está ejecutando muere debido a un SIGINT (es decir, su estado de salida indica que SIGINT lo ha eliminado).

$ bash -c 'sh -c "trap exit\ 0 INT; sleep 10; :"; echo here'
^Chere

Arriba, bash , sh y sleep recibo SIGINT cuando presiono Ctrl-C, pero sh sale normalmente con un código de salida 0, entonces bash ignora el SIGINT, por lo que vemos "aquí".

ping , al menos el de iputils, se comporta así. Cuando se interrumpe, imprime estadísticas y sale con un estado de salida 0 o 1 dependiendo de si sus pings fueron respondidos o no. Entonces, cuando presiona Ctrl-C mientras ping se está ejecutando, bash nota que ha presionado Ctrl-C en sus controladores SIGINT, pero desde ping sale normalmente, bash no sale.

Si agrega un sleep 1 en ese ciclo y presiona Ctrl-C mientras sleep se está ejecutando, porque sleep no tiene un controlador especial en SIGINT, morirá e informará a bash que murió de un SIGINT, y en ese caso bash saldrá (en realidad se matará a sí mismo con SIGINT para informar la interrupción a su padre).

Relacionado:#!/bin/bash – ¿no existe tal archivo o directorio?

En cuanto a por qué bash se comporta así, no estoy seguro y observo que el comportamiento no siempre es determinista. Acabo de hacer la pregunta en bash lista de correo de desarrollo (Actualizar :@Jilles ahora ha precisado el motivo en su respuesta).

El único otro shell que encontré que se comporta de manera similar es ksh93 (Actualización, como lo menciona @Jilles, también lo hace FreeBSD sh ). Allí, SIGINT parece ser claramente ignorado. Y ksh93 sale cada vez que SIGINT cancela un comando.

Obtienes el mismo comportamiento que bash arriba pero también:

ksh -c 'sh -c "kill -INT \$\$"; echo test'

No muestra "prueba". Es decir, sale (suicidándose con SIGINT allí) si el comando que estaba esperando muere de SIGINT, incluso si él mismo no recibió ese SIGINT.

Una solución sería agregar un:

trap 'exit 130' INT

En la parte superior del script para forzar bash para salir al recibir un SIGINT (tenga en cuenta que, en cualquier caso, SIGINT no se procesará sincrónicamente, solo después de que haya salido del comando que se está ejecutando actualmente).

Idealmente, nos gustaría informar a nuestros padres que morimos por un SIGINT (de modo que si es otro bash script por ejemplo, que bash el guión también se interrumpe). Haciendo una exit 130 no es lo mismo que morir de SIGINT (aunque algunos shells establecerán $? al mismo valor para ambos casos), sin embargo, a menudo se usa para informar una muerte por SIGINT (en sistemas donde SIGINT es 2, que es la mayoría).

Sin embargo, para bash , ksh93 o FreeBSD sh , eso no funciona. Ese estado de salida 130 no es considerado como una muerte por SIGINT y una secuencia de comandos principal no cancelar allí.

Entonces, una alternativa posiblemente mejor sería suicidarnos con SIGINT al recibir SIGINT:

trap '
  trap - INT # restore default INT handler
  kill -s INT "$$"
' INT

Linux
  1. nombre base con espacios en un script bash?

  2. Llame al script de Python desde bash con argumento

  3. Script bash inactivo hasta que se registra el evento CTRL+c

  4. Ejecute el comando bash en la canalización de jenkins

  5. Ejecutar un script bash o un binario c en un sistema de archivos con la opción noexec

Cómo escribir un script Bash con ejemplos

Shell Scripting Parte I:Primeros pasos con bash scripting

35 ejemplos de secuencias de comandos Bash

Bash Beginner Series #10:Automatización con Bash

Tutorial de introducción a Bash Scripting con 5 ejemplos prácticos

Bash Script for Loop explicado con ejemplos