2011-01-12 10 views
13

Come si invia un Ctrl-C a più processi ssh -t negli oggetti Popen()?Invia Ctrl-C ai processi remoti avviati tramite sottoprocesso.Popen e ssh

ho qualche codice Python che prende il via uno script su un host remoto:

# kickoff.py 

# i call 'ssh' w/ the '-t' flag so that when i press 'ctrl-c', it get's 
# sent to the script on the remote host. otherwise 'ctrol-c' would just 
# kill things on this end, and the script would still be running on the 
# remote server 
a = subprocess.Popen(['ssh', '-t', 'remote-host', './script.sh', 'a']) 
a.communicate() 

che funziona grande, ma ho bisogno di dare il via più script sulla macchina remota:

# kickoff.py 

a = subprocess.Popen(['ssh', '-t', 'remote-host', './script.sh', 'a']) 
b = subprocess.Popen(['ssh', '-t', 'remote-host', './script.sh', 'b']) 
a.communicate() 
b.communicate() 

Il risultato di questo è che Ctrl-C non uccide in modo affidabile tutto e il mio terminale viene sempre ingarbugliato in seguito (devo eseguire 'reset'). Quindi, come posso uccidere entrambi gli script remoti quando il principale viene ucciso?

Nota: sto tentando di evitare l'accesso all'host remoto, la ricerca di "script.sh" nell'elenco dei processi e l'invio di un SIGINT a entrambi i processi. Voglio solo essere in grado di premere Ctrl-C sullo script di kickoff e fare in modo che entrambi i processi remoti vengano eliminati. Una soluzione meno ottimale può comportare deterministicamente la ricerca dei PID degli script remoti, ma non so come farlo nel mio attuale set-up.

Aggiornamento: lo script che viene avviato sul server remoto avvia effettivamente diversi processi figli e mentre uccide lo ssh uccide lo script remoto originale (probabilmente b/c di SIGHUP), le attività figli non vengono uccise.

+0

ho cambiato il titolo a qualcosa che descrive in realtà ciò che si vuole fare. –

+0

Non ho idea se funzionerà, ma hai provato a inviare la fine del byte di testo "\ x03" al sottoprocesso? È equivalente a Ctrl-C. –

+0

@Thomas K: Buona idea, ma purtroppo funzionerà solo se "\ x03" viene inviato al lato di input di un terminale a cui il processo è collegato (o, naturalmente, se il programma interpreta i dati in quel modo!) .. Purtroppo in questo caso il sottoprocesso avviene tramite una pipe piuttosto che un terminale, quindi la gestione che converte Ctrl-C in SIGINT non è presente :( – psmears

risposta

6

L'unico modo sono stato in grado di uccidere con successo tutti i miei processi figli era utilizzando pexpect:

a = pexpect.spawn(['ssh', 'remote-host', './script.sh', 'a']) 
a.expect('something') 

b = pexpect.spawn(['ssh', 'remote-host', './script.sh', 'b']) 
b.expect('something else') 

# ... 

# to kill ALL of the children 
a.sendcontrol('c') 
a.close() 

b.sendcontrol('c') 
b.close() 

Questo è abbastanza affidabile. Credo che qualcun altro abbia postato questa risposta prima, ma poi ha cancellato la risposta, quindi la posterò nel caso in cui qualcun altro sia curioso.

2

Non ho provato questo, ma forse si può prendere un KeyboardInterrupt e poi uccidere i processi:

try 
    a = subprocess.Popen(['ssh', '-t', 'remote-host', './script.sh', 'a']) 
    b = subprocess.Popen(['ssh', '-t', 'remote-host', './script.sh', 'b']) 
    a.communicate() 
    b.communicate() 
except KeyboardInterrupt: 
    os.kill(a.pid, signal.SIGTERM) 
    os.kill(b.pid, signal.SIGTERM) 
+1

non è possibile uccidere un processo in un computer remoto come quello !! !! – mouad

+0

Gli oggetti popen hanno infatti un metodo '.kill()' ma come dice la singolarità, la sfida è uccidere i processi remoti, non quelli locali. Non penso ci sia un modo semplice per farlo, perché Python conosce solo i processi locali . –

+2

@singularity: hai ragione che uccide il processo locale, ma di solito ucciderà anche i processi remoti (vedi la risposta di BatchyX). – psmears

4

Quando ucciso, ssh invierà un SIGHUP ai processi remoti. Potresti racchiudere i processi remoti in uno script shell o python che li ucciderà quando tale script riceverà un SIGHUP (vedi il comando trap per bash e il modulo del segnale in python)

Potrebbe anche essere possibile farlo con una linea di comando gonfia anziché uno script di wrapper remoto.

Il problema è che uccidere i processi remoti non è quello che vuoi, quello che vuoi è avere un terminale funzionante dopo aver fatto Ctrl + C. per fare ciò, dovrete uccidere i processi remoti E vedere l'output rimanente, che conterrà alcune sequenze di controllo del terminale per ripristinare il terminale in uno stato adeguato. Per questo è necessario un mecanismo per segnalare uno script wrapper per uccidere i processi. Questa non è la stessa cosa

+0

L'azione predefinita per SIGHUP è terminata, quindi potresti non aver nemmeno bisogno di un wrapper. – psmears

+2

@psmears: Se fosse così, il richiedente non avrebbe avuto alcun problema e la domanda non esisterebbe. – BatchyX

+0

Supponevo che ciò fosse dovuto al fatto che i segnali non venivano recapitati localmente correttamente per qualche motivo (ad esempio un ssh veniva lasciato in esecuzione in background in qualche modo). – psmears

0

Ho risolto il problema analizzando questo problema eliminando tutti i segnali a cui tenevo. Quando Ctrl + C viene premuto, verrà comunque passato al sottoprocesso, ma Python attenderà fino all'uscita dal sottoprocesso prima di gestire il segnale nello script principale. Funziona bene per un sottoprocesso di segnale fintanto che il sottoprocesso risponde a Ctrl + C.

class DelayedSignalHandler(object): 
    def __init__(self, managed_signals): 
     self.managed_signals = managed_signals 
     self.managed_signals_queue = list() 
     self.old_handlers = dict() 

    def _handle_signal(self, caught_signal, frame): 
     self.managed_signals_queue.append((caught_signal, frame)) 

    def __enter__(self): 
     for managed_signal in self.managed_signals: 
      old_handler = signal.signal(managed_signal, self._handle_signal) 
      self.old_handlers[managed_signal] = old_handler 

    def __exit__(self, *_): 
     for managed_signal, old_handler in self.old_handlers.iteritems(): 
      signal.signal(managed_signal, old_handler) 

     for managed_signal, frame in self.managed_signals_queue: 
      self.old_handlers[managed_signal](managed_signal, frame) 

Ora, il mio codice sottoprocesso assomiglia a questo:

with DelayedSignalHandler((signal.SIGINT, signal.SIGTERM, signal.SIGHUP)): 
     exit_value = subprocess.call(command_and_arguments) 

Ogni volta che viene premuto Ctrl + C, l'applicazione è permesso di uscire prima che il segnale viene gestito in modo da non dovete preoccuparvi di il terminale viene confuso perché il thread del sottoprocesso non è stato terminato contemporaneamente alla thread del processo principale.

0

C'è un involucro paramiko, ssh_decorate che ha un metodo ctrl

from ssh_decorate import ssh_connect 
ssh = ssh_connect('user','password','server') 
ssh.ctrl('c') 

non potrebbe essere più semplice