问题描述
我正在处理一个 Python 脚本,经过一些准备工作后,它会启动 ssh
。我的脚本实际上是一个小的 CLI 工具。在类 Unix 系统上,在其生命周期结束时,Python 脚本将自身替换为 ssh
客户端,因此用户现在可以直接与 ssh
交互(即在远程机器上运行任意命令等) ):
os.execvpe('ssh',['ssh','-o','foo','user@host'],os.environ)
如果您想知道的话,令人惊喜和旁注:Windows 10 实际上现在内置了 OpenSSH 的本机版本,因此该平台上有一个 ssh
命令。
os.execvpe
存在于 Windows 上的 Python 标准库中,但它不会取代原始 (Python) 进程。情况……有点复杂:1、2、3。底线:Windows 没有实现相应的 POSIX 语义来替换正在运行的进程。
通常的做法是使用 subprocess.Popen
代替,可以有效地创建子进程。我可以启动子进程以便父进程继续运行,或者我可以在父进程死亡时启动子进程(我认为 Windows 确实像类 Unix 系统一样支持后者)。无论哪种方式,用户都无法在命令行中与孩子进行交互。
假设我让父级保持活动状态,我现在必须编写大量代码来通过父级将用户 I/O 传递给/从子级传递,例如 so。后者涉及管理流甚至线程,这取决于它应该表现得有多好 - 很多地方可能会出现潜在问题和故障。我不喜欢这样做(如果可以避免的话)。
如何在所描述的场景中有效地替换 Windows 上的 os.execvpe
?
编辑(1):可能相关的点点滴滴......
- Handle Inheritance I
- Handle Inheritance II
- STARTUPINFO in Windows
- STARTUPINFO in Windows - for Python
我想这取决于在将 STARTUPINFO
对象传递给 Popen
之前弄清楚如何正确配置它。命令行实际上可以在 Windows 中继承。
EDIT (2):通过 pywin32
- ssh
的部分解决方案打开第二个新的 cmd
窗口并且可以与之交互。带有 Python 的原始 shell 保持打开状态,Python 本身退出:
from win32.Demos.winprocess import Process
from shlex import join
Process(join(['ssh','user@host']))
解决方法
部分和不完整的解决方案如下所示,请参阅待办事项评论:
import win32api,win32process,win32con
from shlex import join
si = win32process.STARTUPINFO()
# TODO fix flags
si.dwFlags = win32con.STARTF_USESTDHANDLES ^ win32con.STARTF_USESHOWWINDOW
# inherit stdin,stdout and stderr
si.hStdInput = win32api.GetStdHandle(win32api.STD_INPUT_HANDLE)
si.hStdOutput = win32api.GetStdHandle(win32api.STD_OUTPUT_HANDLE)
si.hStdError = win32api.GetStdHandle(win32api.STD_ERROR_HANDLE)
# TODO fix value?
si.wShowWindow = 1
# TODO set values?
# si.dwX,si.dwY = ...
# si.dwXSize,si.dwYSize = ...
# si.lpDesktop = ...
procArgs = (
None,# appName
join(['ssh','-o','foo','user@host']),# commandLine
None,# processAttributes
None,# threadAttributes
1,# bInheritHandles TODO ?
win32process.CREATE_NEW_CONSOLE,# dwCreationFlags
None,# newEnvironment
None,# currentDirectory
si,# startupinfo
)
procHandles = win32process.CreateProcess(*procArgs) # run ...
ssh
打开第二个新的 cmd.exe
窗口并可以与之交互。包含 Python 的原始 cmd.exe
窗口保持打开状态,Python 本身退出,将控制权返回给 cmd.exe
本身。它是可用的,虽然不一致和丑陋。
我想这归结为正确配置 win32process.STARTUPINFO
,但即使在阅读大量文档之后,我还是无法理解它......
您可以使用 subprocess.Popen
或 subprocess.call
函数代替 os.execvpe
。它们具有标志 shell
,可确保子进程可以获得 stdin
。
我使用以下代码在 Windows 中尝试过:
import os
import subprocess
subprocess.Popen('ssh -o foo user@host',shell=True,env=os.environ)
它有效。