2011-01-12 8 views
13

Jak wysłać Ctrl-C do wielu procesów ssh -t w obiektach Popen()?Wyślij Ctrl-C do zdalnych procesów uruchomionych za pomocą podprocesu.Popen i ssh

Mam niektóre kodu Pythona, który startuje skrypt na zdalnym hoście:

# 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() 

że działa świetnie, ale muszę skopać wiele skryptów na zdalnym komputerze:

# 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() 

Wynikiem tego jest to, że Ctrl-C nie zabija wszystkiego w sposób niezawodny, a mój terminal jest zawsze zniekształcony (muszę uruchomić "reset"). Jak mogę zabić oba zdalne skrypty, gdy główny zostanie zabity?

Uwaga: Staram się unikać logowania do zdalnego hosta, wyszukiwania "script.sh" na liście procesów i wysyłania SIGINT do obu procesów. Chcę tylko nacisnąć Ctrl-C w skrypcie kickoff i zabić oba zdalne procesy. Mniej optymalne rozwiązanie może polegać na deterministycznym znajdowaniu PID skryptów zdalnych, ale nie wiem, jak to zrobić w moim obecnym ustawieniu.

Aktualizacja: skrypt, który jest uruchamiany na serwerze zdalnym, uruchamia kilka procesów potomnych, a podczas usuwania ssh zabija oryginalny skrypt zdalny (prawdopodobnie b/c SIGHUP), zadania podrzędne nie są zabijane.

+0

Zmieniłem tytuł do czegoś, co rzeczywiście opisuje to, co chcesz robić. –

+0

Nie wiem, czy to zadziała, ale czy próbowałeś wysłać koniec bajtu tekstowego "\ x03" do podprocesu? Odpowiada to Ctrl-C. –

+0

@Thomas K: Dobre myślenie, ale niestety to zadziała tylko wtedy, gdy "\ x03" zostanie wysłane do wejścia terminala, do którego dołączony jest proces (lub oczywiście, jeśli program interpretuje dane w ten sposób!). niestety w tym przypadku podproces jest wykonywany przez potok, a nie przez terminal, więc nie ma tam obsługi przekształcającej Ctrl-C w SIGINT :( – psmears

Odpowiedz

6

Jedynym sposobem udało mi się skutecznie zabić wszystkich moich procesów potomnych było za pomocą 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() 

Jest wystarczająco niezawodne. Wierzę, że ktoś inny opublikował tę odpowiedź wcześniej, ale potem usunął odpowiedź, więc opublikuję ją na wypadek, gdyby ktoś inny był ciekawy.

2

nie próbowałem tego, ale może uda Ci się złapać KeyboardInterrupt a następnie zabić procesy:

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

nie można zabić procesu na zdalnym komputerze! !! – mouad

+0

Obiekty Popen w rzeczywistości mają metodę '.kill()', ale, jak mówi pojedynczo, wyzwaniem jest zabicie procesów zdalnych, a nie lokalnych, nie sądzę, że istnieje prosty sposób, aby to zrobić, ponieważ Python zna tylko lokalne procesy . –

+2

@singularity: Masz rację, że to zabija proces lokalny, ale to zazwyczaj zabije również procesy zdalne w konsekwencji (patrz odpowiedź BatchyX). – psmears

4

Kiedy zginął, ssh wyśle ​​SIGHUP do zdalnych procesów. Możesz zawinąć zdalne procesy do skryptu powłoki lub Pythona, który zabije je, gdy skrypt otrzyma SIGHUP (zobacz komendę pułapki dla basha i moduł sygnału w pytonie).

Może być nawet możliwe zrobienie tego za pomocą rozdęta linia poleceń zamiast zdalnego skryptu otoki.

Problem polega na tym, że zabijanie zdalnych procesów nie jest tym, czego potrzebujesz, po czym powinieneś mieć działający terminal po Ctrl + C. aby to zrobić, będziesz musiał zabić procesy zdalne ORAZ zobaczyć pozostały wynik, który będzie zawierał niektóre sekwencje sterujące terminalem, aby zresetować terminal do właściwego stanu. Do tego potrzebny będzie mechanizm mechaniczny, który zasygnalizuje skryptowi opakowania, aby zabił procesy. To nie to samo.

+0

Domyślną akcją dla SIGHUP jest zakończenie, więc możesz nawet nie potrzebować opakowania. – psmears

+2

@psmears: Jeśli tak było, osoba pytająca nie miałaby problemu i pytanie nie istniałoby. – BatchyX

+0

Zakładałem, że to dlatego, że sygnały nie były dostarczane lokalnie poprawnie z jakiegoś powodu (np. Ssh był jakoś pozostawiony w tle). – psmears

0

Rozwiązałem podobny problem, rozwiązując wszystkie sygnały, na które mi zależało. Po naciśnięciu Ctrl + C, nadal będzie przekazywane do podprocesu, ale Python będzie czekał, aż podproces zakończy działanie przed obsługą sygnału w skrypcie głównym. Działa to dobrze dla podprocesu sygnału, o ile podproces odpowiada na 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) 

Teraz mój kod podproces wygląda następująco:

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

Ilekroć Ctrl + C jest wciśnięty, aplikacja może zakończyć przed sygnałem jest obsługiwana, więc nie trzeba się martwić o terminal się zniekształcił, ponieważ wątek podprocesu nie został przerwany w tym samym czasie co główny wątek procesu.

0

Jest wrapper paramiko, ssh_decorate że ma metodę ctrl

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

nie może być prostsze