如何在 Windows 上有效地“替换”`os.execvpe` - 如果“子”进程是交互式命令行应用程序?

问题描述

我正在处理一个 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) 进程。情况……有点复杂:123。底线:Windows 没有实现相应的 POSIX 语义来替换正在运行的进程。

通常的做法是使用 subprocess.Popen 代替,可以有效地创建子进程。我可以启动子进程以便父进程继续运行,或者我可以在父进程死亡时启动子进程(我认为 Windows 确实像类 Unix 系统一样支持后者)。无论哪种方式,用户都无法在命令行中与孩子进行交互。

假设我让父级保持活动状态,我现在必须编写大量代码来通过父级将用户 I/O 传递给/从子级传递,例如 so。后者涉及管理流甚至线程,这取决于它应该表现得有多好 - 很多地方可能会出现潜在问题和故障。我不喜欢这样做(如果可以避免的话)。

如何在所描述的场景中有效地替换 Windows 上的 os.execvpe


编辑(1):可能相关的点点滴滴......

我想这取决于在将 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.Popensubprocess.call 函数代替 os.execvpe。它们具有标志 shell,可确保子进程可以获得 stdin。 我使用以下代码在 Windows 中尝试过:

import os
import subprocess
subprocess.Popen('ssh -o foo user@host',shell=True,env=os.environ)

它有效。