GNU/Linux >> Linux Esercitazione >  >> Linux

Impossibile interrompere uno script Bash con Ctrl+c?

Ho scritto un semplice script bash con un ciclo per la stampa della data e il ping su una macchina 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

Quando lo eseguo da un terminale non riesco a fermarlo con Ctrl+C .
Sembra che invii ^C al terminale, ma lo script non si ferma.

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

Non importa quante volte lo premo o quanto velocemente lo faccio. Non sono in grado di fermarlo.
Fai il test e realizzalo da solo.

Come soluzione secondaria, lo fermo con Ctrl+Z , questo lo interrompe e quindi kill %1 .

Cosa sta succedendo esattamente qui con ^C ?

Risposta accettata:

Quello che succede è che entrambi bash e ping ricevere il SIGINT (bash essere non interattivo, entrambi ping e bash eseguito nello stesso gruppo di processi che è stato creato e impostato come gruppo di processi in primo piano del terminale dalla shell interattiva da cui è stato eseguito lo script).

Tuttavia, bash gestisce SIGINT in modo asincrono, solo dopo che il comando attualmente in esecuzione è terminato. bash esce solo alla ricezione di quel SIGINT se il comando attualmente in esecuzione muore di un SIGINT (cioè il suo stato di uscita indica che è stato ucciso da SIGINT).

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

Sopra, bash , sh e sleep ricevo SIGINT quando premo Ctrl-C, ma sh esce normalmente con un codice di uscita 0, quindi bash ignora il SIGINT, motivo per cui vediamo “qui”.

ping , almeno quello di iputils, si comporta così. Quando viene interrotto, stampa le statistiche ed esce con uno stato di uscita 0 o 1 a seconda che i suoi ping abbiano risposto o meno. Quindi, quando premi Ctrl-C mentre ping è in esecuzione, bash nota che hai premuto Ctrl-C nei suoi gestori SIGINT, ma da ping esce normalmente, bash non esce.

Se aggiungi un sleep 1 in quel ciclo e premi Ctrl-C mentre sleep è in esecuzione, perché sleep non ha un gestore speciale su SIGINT, morirà e riporterà a bash che è morto di un SIGINT, e in quel caso bash uscirà (si ucciderà effettivamente con SIGINT in modo da segnalare l'interruzione al suo genitore).

Correlati:#!/bin/bash – nessun file o directory di questo tipo?

Sul perché bash si comporta così, non ne sono sicuro e noto che il comportamento non è sempre deterministico. Ho appena posto la domanda su bash mailing list di sviluppo (Aggiorna :@Jilles ha ora inchiodato il motivo nella sua risposta).

L'unica altra shell che ho trovato che si comporta in modo simile è ksh93 (Aggiorna, come menzionato da @Jilles, così come FreeBSD sh ). Lì, SIGINT sembra essere chiaramente ignorato. E ksh93 esce ogni volta che un comando viene ucciso da SIGINT.

Ottieni lo stesso comportamento di bash sopra ma anche:

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

Non emette "test". Cioè, esce (uccidendosi con SIGINT lì) se il comando che stava aspettando muore di SIGINT, anche se lui stesso non ha ricevuto quel SIGINT.

Una soluzione sarebbe aggiungere un:

trap 'exit 130' INT

Nella parte superiore dello script per forzare bash di uscire alla ricezione di un SIGINT (si noti che in ogni caso, SIGINT non verrà elaborato in modo sincrono, solo dopo l'uscita del comando attualmente in esecuzione).

Idealmente, vorremmo riferire al nostro genitore che siamo morti per un SIGINT (in modo che se fosse un altro bash script per esempio, quel bash anche lo script viene interrotto). Fare un exit 130 non è come morire di SIGINT (sebbene alcune shell imposteranno $? allo stesso valore per entrambi i casi), tuttavia è spesso utilizzato per segnalare un decesso da SIGINT (su sistemi in cui SIGINT è 2 che è il massimo).

Tuttavia per bash , ksh93 o FreeBSD sh , non funziona. Quel 130 stato di uscita non è considerato una morte da SIGINT e uno script genitore non abortire lì.

Quindi, un'alternativa forse migliore sarebbe ucciderci con SIGINT dopo aver ricevuto SIGINT:

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

Linux
  1. Lo script Bash con `set -e` non si ferma al comando `… &&…`?

  2. Chiama lo script Python da bash con argomento

  3. Autorizzazione negata con bash.sh per eseguire cron

  4. Come uccidere lo script Python con lo script bash

  5. Esecuzione di uno script bash o di un binario c su un file system con l'opzione noexec

Come scrivere uno script Bash con esempi

Shell Scripting Parte I:Introduzione agli script bash

35 Esempi di script Bash

Bash Beginner Series #10:Automazione con Bash

Bash Scripting Introduzione Tutorial con 5 esempi pratici

Script Bash per Loop spiegato con esempi