如何在使用subprocess.popen登录后保持ssh会话打开?

4

我是Python的新手。

我正在尝试SSH到服务器执行一些操作。但在执行这些操作之前,我需要加载一个配置文件,这需要60-90秒的时间。在加载完配置文件后,是否有一种方法可以保持SSH会话开启,以便稍后执行操作?

p = subprocess.Popen("ssh abc@xyz'./profile'", stdout=subprocess.PIPE, shell=True)
result = p.communicate()[0]
print result
return result

这将加载配置文件并退出。有没有办法保持以上ssh会话打开并运行一些命令?

示例:

 p = subprocess.Popen("ssh abc@xyz'./profile'", stdout=subprocess.PIPE, shell=True)
    <More Python Code>
    <More Python Code>
    <More Python Code>
 <Run some scripts/commands on xyz server non-interactively> 

加载配置文件后,我希望能够在远程服务器上运行一些脚本/命令。只需执行以下操作即可实现:
 p = subprocess.Popen("ssh abc@xyz './profile;**<./a.py;etc>**'", stdout=subprocess.PIPE, shell=True)

然而,一旦完成,配置文件就存在了。下一次我想在上述服务器上执行某些脚本时,需要再次加载配置文件(这需要60-90秒)。我正在尝试找出一种方法,可以创建某种隧道(或任何其他方式),在加载配置文件后保持ssh连接开放,以便用户无需等待60-90秒,每当要执行任何操作时。

我无法删除该配置文件。


1
这正是您告诉它要做的事情——通过ssh连接并加载配置文件,这就是为什么它在完成后会关闭。为了保持它的开启状态,您需要像这样使用:ssh -t abc@xyz "bash -l" - ddor254
据我记得,它会在Bash终端中保持SSH开放。 - ddor254
尝试使用 https://rpyc.readthedocs.io/en/latest/ 代替 ssh。 - ddor254
@Koshur:你的意思是无法将主目录中的配置文件复制到一个新文件中,并在该新文件中仅保留与非交互式命令相关的内容吗? - Serge Ballesta
https://dev59.com/tWoy5IYBdhLWcg3wkOsK - Nadeem Taj
显示剩余4条评论
4个回答

1

您需要构建一个像这样的ssh命令['ssh', '-T', 'host_user_name@host_address'],然后按照下面的代码进行操作。

代码:

from subprocess import Popen, PIPE

ssh_conn = ['ssh', '-T', 'host_user_name@host_address']

# if you have to add port then ssh_conn should be as following
# ssh_conn = ['ssh', '-T', 'host_user_name@host_address', '-p', 'port']

commands = """            
    cd Documents/
    ls -l
    cat test.txt
"""

with Popen(ssh_conn, stdin=PIPE, stdout=PIPE, stderr=PIPE, universal_newlines=True) as p:
    output, error = p.communicate(commands)
    print(output)
    print(error)
    print(p.returncode)
    # or can do following things
    p.stdin.write('command_1')
    # add as many command as you want
    p.stdin.write('command_n')

终端输出:

enter image description here

如果您需要进一步的解释,请告诉我。

注意:您可以在命令字符串中添加任意数量的命令。


您可以在命令字符串中添加多个命令,并使用output,error = p.communicate(commands)。此外,在“with”块下,您还可以多次使用output,error = p.communicate(commands) - Sabil
我会给出一个例子作为答案,这样我们就有一个好的回答了 :) - Asara
它有多个命令,但我也可以用 cd Documents; ls -l 来完成。但是我不能多次运行 p.communicate(),因为会出现 ValueError: Cannot send input after starting communication,所以我无法使用同一个连接运行命令、获取输出并再次运行命令并获取输出。 - Asara
我可以在ssh配置中使用Host * ControlMaster auto ControlPersist yes ControlPath ~/.ssh/socket-%r@%h:%p来解决它,但当然我在这里寻找一个Python编程解决方案。 - Asara
请您能否添加更多细节,说明您想要什么,以及目前得到的信息是什么? - Sabil
显示剩余5条评论

1
尝试使用asyncsshspur等ssh库。保留连接对象应该可以保持会话开启。
您还可以发送一个虚拟命令,如date,以防止超时。

0
你想要做的是写入/读取进程的标准输入/输出。
from subprocess import Popen, PIPE
import shlex

shell_command = "ssh user@address"
proc = Popen(shlex.split(shell_command), stdin=PIPE, universal_newlines=True)

# Do python stuff here

proc.stdin.write("cd Desktop\n")
proc.stdin.write("mkdir Example\n")

# And so on

proc.stdin.write("exit\n")

每个命令都必须包含尾随换行符。如果您愿意,print()(自Python 3.x起,它是一个函数)可以使用关键字参数file,这样您就可以忘记换行符(并获得print()的所有好处)。

print("rm Example", file=proc.stdin)

此外,如果你需要查看命令的输出结果,你可以传递`stdout=PIPE`参数,然后通过`proc.stdout.read()`来读取(标准错误同理)。
你可能还想把`exit`命令放在一个try/finally块中,以确保优雅地退出ssh会话。
需要注意的是:a) `read`是阻塞的,所以如果没有输出,它将会一直阻塞,b) 它只会返回那个时间点上从标准输出读取到的内容 - 所以你可能需要重复读取,短暂休眠,或者轮询获取额外的数据。关于将阻塞读取改为非阻塞读取和轮询事件的方法,请参考`fnctl`和`select`标准库模块。

0

你好Koshur!

我认为你试图实现的东西类似于我曾尝试过的,当我试图让我的终端从私人网站可访问时:

我会打开一个bash实例,保持它一直开启,并通过WebSocket连接监听命令。

为了实现这个目的,我在STDOUT上使用了O_NONBLOCK标志。

示例

import fcntl
import os
import shlex
import subprocess

current_process = subprocess.Popen(shlex.split("/bin/sh"), stdin=subprocess.PIPE,
                                   stdout=subprocess.PIPE, stderr=subprocess.STDOUT)  # Open a shell prompt
fcntl.fcntl(current_process.stdout.fileno(), fcntl.F_SETFL,
            os.O_NONBLOCK)  # Non blocking stdout and stderr reading

我会在这之后有一个循环,在另一个线程中检查新的输出:
from time import sleep
from threading import Thread

def check_output(process):
    """
    Checks the output of stdout and stderr to send it to the WebSocket client
    """
    while process.poll() is None: # while the process isn't exited
        try:
            output = process.stdout.read() # Read the stdout PIPE (which contains stdout and stderr)
        except Exception:
            output = None
        if output:
            print(output)
        sleep(.1)
    # from here, we are outside the loop: the process exited
    print("Process exited with return code: {code}".format(code=process.returncode))

Thread(target=check_output, args=(current_process,), daemon=True).start() # Start checking for new text in stdout and stderr

因此,在启动进程时,您需要实现SSH的逻辑:

current_process = subprocess.Popen(shlex.split("ssh abc@xyz'./profile'"), stdin=subprocess.PIPE,
                                   stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

并像这样发送命令:

def send_command(process, cmd):
    process.stdin.write(str(cmd + "\n").encode("utf-8")) # Write the input to STDIN
    process.stdin.flush() # Run the command

send_command(current_process, "echo Hello")

编辑

我尝试查看给定示例的最低Python要求,并发现在您标记的Python 2.7上,Thread(daemon)可能无法正常工作。

如果您确定在退出之前退出线程,则可以忽略daemon并使用Thread(),它适用于2.7。(例如,您可以使用atexit并终止进程)

参考资料


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接