我有下一个代码,接下来会做:
ssh -f -M
的子进程让 ssh 在后台启动共享套接字/tmp/control-channel
来连接 ssh 服务器而无需密码。测试.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
答案实际上隐藏在Python文档subprocess.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 来测试),就是在
timeout
调用中添加 communicate
。