我需要在python中创建一个方法,用于支持超时的远程SSH命令执行(在超时的情况下从stdout
和stderr
收集部分输出)。我在这里浏览了很多主题,但我找不到任何完整的解决方案。
在下面你将找到关于如何在python 3中实现这一点的建议。
答案 0 :(得分:0)
我使用(略微调整)Thomas' solution for timeout,但如果需要可以使用其他方法:
import paramiko
import signal
class Timeout:
def __init__(self, seconds=1, error_message='Timeout', exception_type=TimeoutError):
self.seconds = seconds
self.error_message = error_message + " after {seconds}".format(seconds=seconds)
self.exception_type = exception_type
def handle_timeout(self, signum, frame):
raise self.exception_type(self.error_message)
def __enter__(self):
signal.signal(signal.SIGALRM, self.handle_timeout)
signal.alarm(self.seconds)
def __exit__(self, type, value, traceback):
signal.alarm(0)
def run_cmd(connection_info, cmd, timeout=5):
"""
Runs command via SSH, supports timeout and partian stdout/stderr gathering.
:param connection_info: Dict with 'hostname', 'username' and 'password' fields
:param cmd: command to run
:param timeout: timeout for command execution (does not apply to SSH session creation!)
:return: stdout, stderr, exit code from command execution. Exit code is -1 for timed out commands
"""
# Session creation can be done ous
client = paramiko.SSHClient()
client.load_system_host_keys()
client.connect(**connection_info)
channel = client.get_transport().open_session() # here TCP socket timeout could be tuned
out = ""
err = ""
ext = -1 # exit code -1 for timed out commands
channel.exec_command(cmd)
try:
with Timeout(timeout):
while True:
if channel.recv_ready():
out += channel.recv(1024).decode()
if channel.recv_stderr_ready():
err += channel.recv_stderr(1024).decode()
if channel.exit_status_ready() and not channel.recv_ready() and not channel.recv_stderr_ready():
ext = channel.recv_exit_status()
break
except TimeoutError:
print("command timeout")
return out, err, ext
现在让我们测试一下:
from time import time
def test(cmd, timeout):
credentials = {"hostname": '127.0.0.1', "password": 'password', "username": 'user'}
ts = time()
print("\n---\nRunning: " + cmd)
result = run_cmd(credentials, cmd, timeout)
print("...it took: {0:4.2f}s".format(time() - ts))
print("Result: {}\n".format(str(result)[:200]))
# Those should time out:
test('for i in {1..10}; do echo -n "OUT$i "; sleep 0.5; done', 2)
test('for i in {1..10}; do echo -n "ERR$i " >&2; sleep 0.5; done', 2)
test('for i in {1..10}; do echo -n "ERR$i " >&2; echo -n "OUT$i "; sleep 0.5; done', 2)
# Those should not time out:
test('for i in {1..10}; do echo -n "OUT$i "; sleep 0.1; done', 2)
test('for i in {1..10}; do echo -n "ERR$i " >&2; sleep 0.1; done', 2)
test('for i in {1..10}; do echo -n "ERR$i " >&2; echo -n "OUT$i "; sleep 0.1; done', 2)
# Large output testing, with timeout:
test("cat /dev/urandom | base64 |head -n 1000000", 2)
test("cat /dev/urandom | base64 |head -n 1000000 >&2", 2)