Como canalizar um comando bash e manter Ctrl+C funcionando?

Como canalizar um comando bash e manter Ctrl+C funcionando?

Considerando um software de linha de comando personalizado (por exemplo, loopHelloWorld) que detecta ctrl+C para um bom desligamento. Como canalizar a resposta sem perder o Ctrl+C?

$ loopHelloWorld
    - Ctrl+C to nicely shutdown

Mas com o pipe, o pipe mata o software sem um bom desligamento

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

Exemplo

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

Responder1

Ctrl+Cfaz com que um SIGINT seja enviado para todos os processos no pipeline (pois todos são executados no mesmo grupo de processos que corresponde ao trabalho em primeiro plano do seu shell interativo).

Então em:

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

Tanto o processo em execução loopHelloWorldquanto aquele que executa o subshell que executa o whileloop obterão o arquivo SIGINT.

Se loopHelloWorldescrever a Ctrl+C to nicely shutdownmensagem em seu stdout, ela também será gravada no pipe. Se isso fordepoiso subshell na outra extremidade já morreu, então loopHelloWorldirátambémreceba um SIGPIPE, com o qual você precisará lidar.

Aqui, você deve escrever essa mensagem em stderr, pois não é a saída normal do seu comando (embora não se aplique ao pingexemplo). Então não passaria pelo cano.

Ou você pode fazer com que o subshell executando o loop while ignore o SIGINT para que continue lendo a loopHelloWorldsaída após o SIGINT:

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

no entanto, isso faria com que o status de saída do pipeline fosse 0 quando você pressionasse Ctrl+C.

Outra opção para esse exemplo específico seria usar zshou ksh93em vez de bash. Nesses shells, o whileloop seria executado no processo principal do shell, portanto não seria afetado pelo SIGINT.

Isso não ajudaria loopHelloWorld | catonde cate loopHelloWorldexecutar no grupo de processos em primeiro plano.

Responder2

Use uma armadilha:

#! /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

informação relacionada