2011-01-12 26 views
13

Làm cách nào để gửi Ctrl-C đến nhiều quá trình ssh -t trong các đối tượng Popen()?Gửi Ctrl-C tới các quá trình từ xa bắt đầu qua tiến trình con.Popen và ssh

Tôi có một số mã Python khởi động một kịch bản trên một máy chủ từ xa:

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

đó làm việc tuyệt vời, nhưng tôi cần phải khởi nhiều kịch bản trên máy chủ từ xa:

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

Kết quả của việc này là Ctrl-C không giết chết đáng tin cậy mọi thứ, và thiết bị đầu cuối của tôi luôn bị cắt xén sau đó (tôi phải chạy 'reset'). Vậy làm thế nào tôi có thể giết cả hai kịch bản từ xa khi kịch bản chính bị giết?

Lưu ý: Tôi đang cố gắng tránh đăng nhập vào máy chủ từ xa, tìm kiếm 'script.sh' trong danh sách quy trình và gửi SIGINT cho cả hai quy trình. Tôi chỉ muốn có thể nhấn Ctrl-C trên tập lệnh khởi động và có thể giết cả hai quy trình từ xa. Một giải pháp ít tối ưu có thể liên quan đến việc xác định PID của các tập lệnh từ xa, nhưng tôi không biết cách thực hiện điều đó trong thiết lập hiện tại của mình.

Cập nhật: tập lệnh khởi động trên máy chủ từ xa thực sự khởi động một số quy trình con và khi giết ssh sẽ giết tập lệnh từ xa ban đầu (có thể là b/c của SIGHUP), nhiệm vụ trẻ em không bị giết.

+0

Tôi đã thay đổi tiêu đề để cái gì đó thực sự mô tả những gì bạn muốn làm. –

+0

Không có ý tưởng nếu nó sẽ làm việc, nhưng có bạn đã cố gắng gửi kết thúc của byte văn bản "\ x03" để các subprocess? Tương đương với Ctrl-C. –

+0

@Thomas K: Suy nghĩ tốt, nhưng tiếc là sẽ chỉ hoạt động nếu "\ x03" được gửi đến phía đầu vào của thiết bị đầu cuối được gắn vào (hoặc tất nhiên nếu chương trình diễn giải dữ liệu theo cách đó!) .. Thật đáng buồn trong trường hợp này subprocess là thông qua một đường ống chứ không phải là một thiết bị đầu cuối, do đó, việc xử lý chuyển đổi Ctrl-C thành SIGINT không có: ( – psmears

Trả lời

6

Cách duy nhất tôi đã có thể giết chết thành công tất cả các quá trình con tôi là bằng cách sử dụng 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() 

Đây là đủ tin cậy. Tôi tin rằng ai đó đã đăng câu trả lời này trước đó, nhưng sau đó đã xóa câu trả lời, vì vậy tôi sẽ đăng câu trả lời trong trường hợp người khác tò mò.

2

Tôi đã không cố gắng này, nhưng có lẽ bạn có thể bắt một KeyboardInterrupt và sau đó giết các quá trình:

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

bạn không thể giết chết một quá trình trong một máy tính từ xa như thế !! – mouad

+0

Các đối tượng Popen trên thực tế có một phương thức '.kill()' Nhưng như sự kỳ dị nói, thách thức là giết các tiến trình từ xa, chứ không phải là các tiến trình từ xa, tôi không nghĩ có cách đơn giản để làm điều đó, bởi vì Python chỉ biết về các quy trình cục bộ . –

+2

@singularity: Bạn nói đúng rằng nó giết chết quá trình cục bộ, nhưng điều đó thường sẽ giết chết các quá trình từ xa cũng như một hệ quả (xem câu trả lời của BatchyX). – psmears

4

Khi bị giết, ssh sẽ gửi một SIGHUP để các quá trình từ xa. Bạn có thể bọc các quy trình từ xa vào một kịch bản shell hoặc python sẽ giết chúng khi kịch bản đó nhận được SIGHUP (xem lệnh bẫy cho bash, và mô-đun tín hiệu trong python)

Nó thậm chí có thể thực hiện được với một dòng lệnh cồng kềnh thay vì một kịch bản trình bao bọc từ xa.

Vấn đề là giết chết các quá trình từ xa không phải là những gì bạn muốn, những gì bạn muốn là có một thiết bị đầu cuối làm việc sau khi bạn làm Ctrl + C. để làm điều đó, bạn sẽ phải tiêu diệt các quá trình từ xa VÀ xem đầu ra còn lại, sẽ chứa một số chuỗi điều khiển đầu cuối để thiết lập lại thiết bị đầu cuối về trạng thái thích hợp. Cho rằng bạn sẽ cần một mecanism để báo hiệu một kịch bản wrapper để giết các quá trình. Đây không phải là điều tương tự.

+0

Hành động mặc định cho SIGHUP là chấm dứt, vì vậy bạn thậm chí có thể không cần trình bao bọc. – psmears

+2

@psmears: Nếu đúng như vậy, người hỏi sẽ không gặp vấn đề gì và câu hỏi sẽ không tồn tại. – BatchyX

+0

Tôi đã giả định rằng đó là vì các tín hiệu không được phân phối cục bộ một cách chính xác vì một số lý do (ví dụ: một ssh đang bị bỏ chạy dưới nền nào đó). – psmears

0

Tôi đã làm việc xung quanh vấn đề tương tự vấn đề này bằng cách unmapping tất cả các tín hiệu tôi quan tâm. Khi nhấn Ctrl + C, nó sẽ vẫn được chuyển qua cho tiến trình con nhưng Python sẽ đợi cho đến khi tiến trình con thoát ra trước khi xử lý tín hiệu trong kịch bản lệnh chính. Điều này làm việc tốt cho một subprocess tín hiệu miễn là tiến trình con đáp ứng với 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) 

Bây giờ, mã subprocess của tôi trông như thế này:

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

Bất cứ khi nào Ctrl + C được nhấn, các ứng dụng được phép để thoát trước khi tín hiệu được xử lý, do đó bạn không cần phải lo lắng về các thiết bị đầu cuối bị cắt xén vì thread subprocess không được chấm dứt tại cùng một thời điểm như là quá trình chủ đề chính.

0

Có một wrapper paramiko, ssh_decorate rằng có một phương pháp ctrl

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

Không thể được đơn giản

Các vấn đề liên quan