当按下 Ctrl+C 时,从 Windows Python Paramiko 脚本优雅地中止通过 SSH 执行的远程 Windows 命令

Gracefully abort remote Windows command executed over SSH from Windows Python Paramiko script when Ctrl+C is pressed

我有一个跟进问题,它建立在我在这里提出的问题的基础上:,已经回答了。

感谢上面link的回答,我的python脚本如下:

# SSH.py

import paramiko
import argparse
import os

path = "path"
python_script = "worker.py"

# definitions for ssh connection and cluster
ip_list = ['XXX.XXX.XXX.XXX', 'XXX.XXX.XXX.XXX', 'XXX.XXX.XXX.XXX']
port_list = [':XXXX', ':XXXX', ':XXXX']
user_list = ['user', 'user', 'user']
password_list = ['pass', 'pass', 'pass']
node_list = list(map(lambda x: f'-node{x + 1} ', list(range(len(ip_list)))))
cluster = ' '.join([node + ip + port for node, ip, port in zip(node_list, ip_list, port_list)])

# run script on command line of local machine
os.system(f"cd {path} && python {python_script} {cluster} -type worker -index 0 -batch 64 > {path}/logs/'command output'/{ip_list[0]}.log 2>&1")

# loop for IP and password
stdouts = []

clients = []

for i, (ip, user, password) in enumerate(zip(ip_list[1:], user_list[1:], password_list[1:]), 1):
    try:
        print("Open session in: " + ip + "...")
        client = paramiko.SSHClient()
        client.connect(ip, user, password)
    except paramiko.SSHException:
        print("Connection Failed")
        quit()

    try:
        path = f"C:/Users/{user}/Desktop/temp-ines"

        stdin, stdout, stderr = ssh.exec_command(
    
              f"cd {path} && python {python_script} {cluster} -type worker -index {i} -batch          64>"

              f"C:/Users/{user}/Desktop/{ip}.log 2>&1 &"
)
        
clients.append(ssh)

        stdouts.append(stdout)
    except paramiko.SSHException:
        print("Cannot run file. Continue with other IPs in list...")
        client.close()
        continue

# Wait for commands to complete

for i in range(len(stdouts)):
 
        print("hello")
        stdouts[i].read()
 
        print("hello1")   
        clients[i].close()
        print('hello2")

print("\n\n***********************End execution***********************\n\n")

这个在本地 运行 的脚本能够通过 SSH 连接到服务器和 运行 命令(即 运行 一个名为 worker.py 并将命令输出记录到日志文件中)。也就是说,它能够毫无问题地通过第一个 for 循环。

我的问题与第二个 for 循环有关。请查看我在第二个 for 循环中添加的打印语句,以便清楚。当我在本地 运行 SSH.py 时,这是我观察到的:

如您所见,我通过 ssh 连接到每台服务器,然后继续读取我通过 ssh 连接到的第一台服务器的命令输出。 worker.py 脚本可能需要 30 分钟左右才能完成,并且每个服务器上的命令输出都是相同的——因此需要 30 分钟来读取第一台服务器的命令输出,然后关闭第二台服务器的 SSH 连接第一台服务器,花几秒钟读取第二台服务器的命令输出(因为它与第一台服务器相同,并且已经完全打印出来),关闭其 SSH 连接,依此类推。如果有帮助,请查看下面的一些命令行输出。

现在,我的问题是,如果我不想等到 worker.py 脚本完成,即整整 30 分钟怎么办?我 cannot/do 不知道如何提出 KeyboardInterrupt exception。我尝试的是退出本地 SSH.py 脚本。但是,正如您从打印语句中看到的那样,这不会关闭 SSH 连接,尽管训练和日志文件将停止记录信息。此外,在我退出本地 SSH.py 脚本后,如果我尝试删除任何日志文件,我会收到一条错误消息“无法删除文件,因为它正在 cmd.exe 中使用”——这个只是有时会发生,我相信这是因为没有关闭 SSH 连接?


python 控制台中的第一个 运行:

挂起:本地 python 和日志文件 运行 正在保存,但没有打印语句,也没有 python 并且日志文件 run/saved 在服务器中。

我再次运行它所以第二个进程开始:

现在,第一个进程不再挂起(python 运行ning 和日志文件保存在服务器中)。并且可以关闭这一秒run/process。这就像第二个 run/process 有助于第一个 run/process.

的挂起

如果我在终端中 运行 python SSH.py 它就会挂起。

以前没有这种情况。

如果您知道 SSHClient.close 完全关闭连接并中止远程命令,请在响应 KeyboardInterrupt 时调用它。

为此,您不能使用 stdout.read 的简单解决方案,因为它会阻止并阻止处理 Windows 上的 Ctrl+C

  • 使用我对 的回答中的等待代码(while any(x is not None for x in stdouts): 片段)。

  • 并将其包装到try:...except (KeyboardInterrupt):.

try:
    while any(x is not None for x in stdouts):
        for i in range(len(stdouts)):
            stdout = stdouts[i]
            if stdout is not None:
                channel = stdout.channel
                # To prevent losing output at the end, first test for exit,
                # then for output
                exited = channel.exit_status_ready()
                while channel.recv_ready():
                    s = channel.recv(1024).decode('utf8')
                    print(f"#{i} stdout: {s}")
                while channel.recv_stderr_ready():
                    s = channel.recv_stderr(1024).decode('utf8')
                    print(f"#{i} stderr: {s}")
                if exited:
                    print(f"#{i} done")
                    clients[i].close()
                    stdouts[i] = None
        time.sleep(0.1)
except (KeyboardInterrupt):
    print("Aborting")
    for i in range(len(clients)):
        print(f"#{i} closing")
        clients[i].close()

如果不需要stdout和stderr的分离,使用Channel.set_combine_stderr. See .

可以大大简化代码