我有下一个代码,它会做下一个:
ssh -f -M
让ssh在后台启动共享套接字的子进程
1.由于上面是在后台,所以对于第二个ssh连接,我们可以重用socket/tmp/control-channel
来连接ssh服务器,而不需要密码。
test.py:
import subprocess
import os
import sys
import stat
ssh_user = "my_user" # change to your account
ssh_passwd = "my_password" # change to your password
try:
os.remove("/tmp/control-channel")
except:
pass
# prepare passwd file
file = open("./passwd","w")
passwd_content = f"#!/bin/sh\necho {ssh_passwd}"
file.write(passwd_content)
file.close()
os.chmod("./passwd", stat.S_IRWXU)
# setup shared ssh socket, put it in background
env = {'SSH_ASKPASS': "./passwd", 'DISPLAY':'', 'SSH_ASKPASS_REQUIRE':'force'}
args = ['ssh', '-f', '-o', 'LogLevel=ERROR', '-x', '-o', 'ConnectTimeout=30', '-o', 'ControlPersist=300', '-o', 'UserKnownHostsFile=/dev/null', '-o', 'StrictHostKeyChecking=no', '-o', 'ServerAliveInterval=15', '-MN', '-S', '/tmp/control-channel', '-p', '22', '-l', ssh_user, 'localhost']
process = subprocess.Popen(args, env=env,
stdout=subprocess.PIPE,
# stderr=subprocess.STDOUT, # uncomment this line to enable stderr will make subprocess hang
stdin=subprocess.DEVNULL,
start_new_session=True)
sout, serr = process.communicate()
print(sout)
print(serr)
# use shared socket
args2 = ['ssh', '-o', 'LogLevel=ERROR', '-o', 'ControlPath=/tmp/control-channel', '-p', '22', '-l', ssh_user, 'localhost', 'uname -a']
process2 = subprocess.Popen(args2,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
stdin=subprocess.DEVNULL)
content, _ = process2.communicate()
print(content)
执行:
$ python3 test.py
b''
None
b'Linux shmachine 4.19.0-21-amd64 #1 SMP Debian 4.19.249-2 (2022-06-30) x86_64 GNU/Linux\n'
到目前为止一切顺利,如果我在第一个子进程中取消注解stderr=subprocess.STDOUT
,它将挂起:
$ python3 test.py
^CTraceback (most recent call last):
File "test.py", line 29, in <module>
sout, serr = process.communicate()
File "/usr/lib/python3.7/subprocess.py", line 926, in communicate
stdout = self.stdout.read()
KeyboardInterrupt
我想知道这里有什么问题?
我的环境:
$ python3 --version
Python 3.7.3
$ ssh -V
OpenSSH_7.9p1 Debian-10+deb10u2, OpenSSL 1.1.1n 15 Mar 2022
$ cat /etc/issue
Debian GNU/Linux 10 \n \l
更新:我看到this post类似于我的问题,但没有答案。
问题2:将communicate
更改为wait
使其工作,但pipe size which wait use
肯定小于memory size which communicate use
,所以我仍然想知道为什么我不能使其与communicate
一起工作。
3条答案
按热度按时间qmelpv7a1#
答案其实就隐藏在python文档子进程.Popen中,在
Popen.communicate
的文档中,读到以下内容:Note: The data read is buffered in memory, so do not use this method if the data size is large or unlimited.
因此,一个(潜在的)可行的解决方案(因为我实际上没有一个可以测试的SSH)是向
communicate
调用添加一个timeout
。62lalag42#
我删除了stdout管道集,只留下stderr进行最小的检查,最后使用strace来确认这是在ssh 8.4中修复的Y2020旧ssh的bug。所以巨蟒确实纠正了行为。。
1.我看到它被卡住了,就像下一个:
1.这意味着
ssh -f
在将ssh置于后台时没有关闭stderr,因此communicate
无法读取stderr的一个字节,然后挂起为next:我用openssh commit确认
jxct1oxe3#
问题是,即使在调用
communicate()
方法之后,ssh -f
命令仍将继续在后台运行。这是因为communicate()
方法只等待进程完成对标准输出和标准错误流的写入。它不会等待进程实际终止。当您将
ssh -f
命令的标准错误流重定向到其标准输出流时,communicate()
方法将永远不会返回。这是因为ssh -f
命令只要运行就会继续写入其标准输出流。要解决此问题,您需要:
1.调用
process.terminate()
方法显式终止ssh -f
命令。1.使用
communicate()
方法的timeout
关键字参数来指定超时时间,超过该时间,即使进程仍在运行,communicate()
方法也会返回。下面是如何使用
process.terminate()
方法解决问题的示例:在使用共享套接字之前,此代码将等待
ssh -f
命令启动长达10秒。如果ssh -f
命令没有在10秒内启动,代码将引发TimeoutError
异常。一旦共享套接字可用,代码将使用它连接到SSH服务器并运行
uname -a
命令。uname -a
命令的输出将被打印到控制台。最后,代码将使用
process.terminate()
方法终止ssh -f
命令。