2014-07-18 13 views
6

Obiettivo finale: Lo script BASH in attesa della fine dei lavori in background non viene interrotto il primo Ctrl-c; invece, richiede un secondo Ctrl-c per uscire.bash: è possibile richiedere il doppio Ctrl-c per uscire da uno script?

Sono perfettamente a conoscenza di come funziona il trap integrato in BASH. È possibile:

  1. Si usa per ignorare un segnale complesso (ad esempio, trap '' 2) ... o

  2. che consente di orientare comandi arbitrari eseguiti prima che un segnale funzione originale è permesso che accada (es , trap cmd 2, dove cmd viene eseguito prima che lo script genitore sarà interrotto a causa di SIGINT)

Quindi la domanda si riduce a questo:

012.351.

Come posso efficacemente combinare & insieme, cioè, impedire il risultato finale un segnale comporterebbe (- ad esempio, arrestare script annullando causa SIGINT) ma anche di fare che il segnale causa qualcos'altro (- ad esempio, incrementa un contatore, controlla il contatore e condizionalmente stampa un avviso o esce).

Più semplicemente:

Come posso fare un segnale di fare qualcosa di completamente diverso; non basta inserire un lavoro prima che faccia la sua cosa.

Ecco alcuni esempi di codice per dimostrare a cosa sto mirando; tuttavia, ovviamente non funziona, perché trap può fare solo o dall'alto.

#!/bin/bash 
declare -i number_of_times_trap_triggered 
cleanup_bg_jobs() { 
    number_of_times_trap_triggered+=1 
    if [[ ${number_of_times_trap_triggered} -eq 1 ]]; then 
     echo "There are background jobs still running" 
     echo "Hit Ctrl-c again to cancel all bg jobs & quit" 
    else 
     echo "Aborting background jobs" 
     for pid in ${bg_jobs}; do echo " Killing ${pid}"; kill -9 ${pid}; done 
    fi 
} 
f() { sleep 5m; } 
trap cleanup_bg_jobs 2 
bg_jobs= 
for job in 1 2 3; do 
    f & 
    bg_jobs+=" $!" 
done 
wait 

Quindi questo è l'uscita si finisce per ottenere quando si preme Ctrl-c volta.

[rsaw:~]$ ./zax 
^CThere are background jobs still running 
Hit Ctrl-c again to cancel all bg jobs & quit 
[rsaw:~]$ ps axf|tail -6 
24569 pts/3 S  0:00 /bin/bash ./zax 
24572 pts/3 S  0:00 \_ sleep 5m 
24570 pts/3 S  0:00 /bin/bash ./zax 
24573 pts/3 S  0:00 \_ sleep 5m 
24571 pts/3 S  0:00 /bin/bash ./zax 
24574 pts/3 S  0:00 \_ sleep 5m 

Naturalmente potrei modificare che per ripulire i lavori sul primo Ctrl-c, ma non è quello che voglio. Voglio impedire a BASH di uscire dopo che la prima trappola è stata attivata ... fino a quando non viene attivata una seconda volta.

PS: Piattaforma di destinazione è Linux (io non poteva fregare di meno rispetto POSIX) con BASH v4 +

risposta

1

Un collega (Grega) mi ha appena dato una soluzione che ... beh, non posso credere di non averlo pensato prima.

"Il mio approccio sarebbe ... quello di gettare fuori per abbastanza a lungo, forse per sempre, usando una funzione che non restituisce mai solo o qualcosa del genere (un'altra attesa?), In modo che il secondo gestore può fare il suo lavoro propriamente."

Per la cronaca, wait non funzionava qui. (Ricorsivo.) Tuttavia, l'aggiunta di un comando sleep alla funzione cleanup_bg_jobs() del mio codice originale si sarebbe occupata di esso .. ma avrebbe portato a processi orfani. Quindi ho sfruttato i gruppi di processi per garantire che tutti i bambini del copione siano davvero uccisi. esempio semplificato per i posteri:

#!/bin/bash 
declare -i count= 
handle_interrupt() { 
    count+=1 
    if [[ ${count} -eq 1 ]]; then 
     echo "Background jobs still running" 
     echo "Hit Ctrl-c again to cancel all bg jobs & quit" 
     sleep 1h 
    else 
     echo "Aborting background jobs" 
     pkill --pgroup 0 
    fi 
} 
f() { tload &>/dev/null; } 
trap handle_interrupt 2 
for job in 1 2 3; do 
    f & 
done 
wait 
4

ho fatto qualcosa di simile here e si rompe in gran parte a questo:

ATTEMPT=0 
handle_close() { 
    if [ $ATTEMPT -eq 0 ]; then 
     ATTEMPT=1 
     echo "Shutdown." 
    else 
     echo "Already tried to shutdown. Killing." 
     exit 0 
    fi 
} 
trap handle_close SIGINT SIGTERM 

È puoi impostare una variabile nel tuo gestore che puoi controllare di nuovo la prossima volta che viene intrappolato.

+0

Di solito in questo caso, i due Ctrl + C devono essere continui per interrompere il programma. Quindi, una soluzione migliore dovrebbe riportare ATTEMP a 0 quando viene catturato un tasto giù. –

+0

Grazie per aver provato Wally, ma quello che stai offrendo in realtà non risponde a quello che ho chiesto. Si spegnerà dopo che è stato eseguito il primo comando echo Shutdown. – rsaw

+0

PS: Ho aggiornato il mio post con alcuni dei miei codici di esempio per mostrare che 'trap' non funziona come pensi che funzioni. (A meno che non sia pazzo.) – rsaw

3

ho avuto un caso d'uso leggermente diversa, e voleva lasciare la soluzione qui, come Google mi ha portato a questo argomento. È possibile mantenere l'esecuzione di un comando e consentire all'utente di riavviare con un CTRL+C, e uccidere con doppio CTRL+C nel modo seguente:

trap_ctrlC() { 
    echo "Press CTRL-C again to kill. Restarting in 2 second" 
    sleep 2 || exit 1 
} 

trap trap_ctrlC SIGINT SIGTERM 

while true; do 
    ... your stuff here ... 
done 
+0

questo è bellissimo. tks – cleison

0
  1. usarlo per hanno comandi arbitrari eseguiti prima di una funzione originale segnali è permesso che accada (per esempio, cmd trap 2, dove cmd viene eseguito prima dello script genitore sarà interrotto a causa di SIGINT)

Ital la parte icizzata di quanto sopra non è corretta. Un gestore trap viene eseguito anziché lasciando che SIGINT (o qualsiasi altra cosa) interrompa il processo. Più precisamente:

  • l'azione predefinita SIGINT (e della maggior parte, ma non tutti, altri segnali) è di terminare il processo
  • trap "command" SIGINT cause command da eseguire al posto di (non nonché) l'azione predefinita

Quindi, con il gestore SIGINT installato, SIGINT non interrompe l'intero script. Ma lo fa interrompe il comando wait. Al termine del gestore trap, lo script riprende dopo lo wait, ovvero cade alla fine ed esce normalmente. Si può vedere questo con l'aggiunta di un po 'di codice di debug:

echo Waiting 
wait 
echo Back from wait 
exit 55     # Arbitrary value that wouldn't otherwise occur 

Questa versione produce il seguente:

$ foo 
Waiting 
^CThere are background jobs still running 
Hit Ctrl-c again to cancel all bg jobs & quit 
back from wait 
$ echo $? 
55 
$ 

Quello che dovete fare è ripetere la wait dopo il ritorno del gestore.Questa versione:

#!/bin/bash 
declare -i number_of_times_trap_triggered 
cleanup_bg_jobs() { 
    number_of_times_trap_triggered+=1 
    if [[ ${number_of_times_trap_triggered} -eq 1 ]]; then 
     echo "There are background jobs still running" 
     echo "Hit Ctrl-c again to cancel all bg jobs & quit" 
    else 
     echo "Aborting background jobs" 
     for pid in ${bg_jobs}; do echo " Killing ${pid}"; kill -9 ${pid}; done 
     exit 1 
    fi 
} 
f() { sleep 5m; } 
trap cleanup_bg_jobs 2 
bg_jobs= 
for job in 1 2 3; do 
    f & 
    bg_jobs+=" $!" 
done 

while [ 1 ]; do 
    echo Waiting 
    wait 
    echo Back from wait 
done 

fa come da voi richiesto:

$ ./foo 
Waiting 
^CThere are background jobs still running 
Hit Ctrl-c again to cancel all bg jobs & quit 
Back from wait 
Waiting 
^CAborting background jobs 
    Killing 24154 
    Killing 24155 
    Killing 24156 
$ 

Note:

  • ho lasciato nel roba di debug; ovviamente lo rimuoverai in produzione
  • Il gestore ora fa exit 1 dopo aver eliminato i sottoprocessi. Ecco cosa esce dal ciclo principale infinito
Problemi correlati