¿Cómo canalizar un comando bash y mantener Ctrl+C funcionando?

¿Cómo canalizar un comando bash y mantener Ctrl+C funcionando?

Considerando un software de línea de comando personalizado (por ejemplo, loopHelloWorld) que detecta Ctrl+C para un apagado agradable. ¿Cómo canalizar la respuesta sin perder el Ctrl+C?

$ loopHelloWorld
    - Ctrl+C to nicely shutdown

Pero con la tubería, la tubería mata el software sin un cierre agradable.

$ loopHelloWorld | 
    while IFS= read -r line; do
        echo "$line"
    done

Ejemplo

ping example.com |
  while IFS= read -r line; do
    echo "$line"
  done

Respuesta1

Ctrl+Chace que se envíe un SIGINT a todos los procesos en la canalización (ya que todos se ejecutan en el mismo grupo de procesos que corresponde a ese trabajo de primer plano de su shell interactivo).

Entonces en:

loopHelloWorld | 
  while IFS= read -r line; do
    echo "$line"
  done

Tanto el proceso en ejecución loopHelloWorldcomo el que ejecuta el subshell que ejecuta el whilebucle obtendrán el archivo SIGINT.

Si loopHelloWorldescribe el Ctrl+C to nicely shutdownmensaje en su salida estándar, también se escribirá en la tubería. si eso esdespuésla subcapa en el otro extremo ya ha muerto, loopHelloWorldentoncestambiénrecibir un SIGPIPE, que necesitarías manejar.

Aquí, debes escribir ese mensaje en stderr ya que no es la salida normal de tu comando ( pingaunque no se aplica al ejemplo). Entonces no pasaría por la tubería.

O podría hacer que el subshell que ejecuta el bucle while ignore el SIGINT para que siga leyendo la loopHelloWorldsalida después del SIGINT:

loopHelloWorld | (
  trap '' INT
  while IFS= read -r line; do
    printf '%s\n' "$line"
  done
)

Sin embargo, eso causaría que el estado de salida de la tubería sea 0 cuando presione Ctrl+C.

Otra opción para ese ejemplo específico sería usar zsho ksh93en lugar de bash. En esos shells, el whilebucle se ejecutaría en el proceso principal del shell, por lo que SIGINT no lo afectaría.

Eso no ayudaría a loopHelloWorld | catdeterminar dónde caty loopHelloWorldejecutarlo en el grupo de procesos de primer plano.

Respuesta2

Usa una trampa:

#! /bin/bash

trap handleInt SIGINT

interrupted=0

function handleInt {
    echo "Interrupted..."
    interrupted=1
}


while true
do
    [[ interrupted -ne 0 ]] && { echo "Ctrl-C caught, exiting..." ; exit ; }
    echo "Sleeping..."
    sleep 1
    read -r line
    echo "$line"
done

información relacionada