如何在 Windows 上有效地“替换”`os.execvpe` - 如果“子”进程是交互式命令行应用程序?
Posted
技术标签:
【中文标题】如何在 Windows 上有效地“替换”`os.execvpe` - 如果“子”进程是交互式命令行应用程序?【英文标题】:How to "replace" `os.execvpe` on Windows efficiently - if the "child" process is an interactive command line application? 【发布时间】:2021-07-26 00:33:18 【问题描述】:我正在处理一个 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 中继承。
编辑(2):通过pywin32
- ssh
的部分解决方案打开第二个新的cmd
窗口并且可以与之交互。带有 Python 的原始 shell 保持打开状态,Python 本身退出:
from win32.Demos.winprocess import Process
from shlex import join
Process(join(['ssh', '-o', 'foo', 'user@host']))
【问题讨论】:
您可能需要查看pexpect.readthedocs.io/en/stable。 @CristiFati Wrappingssh
in expect
- 这是“手动”通过所有用户 I/O 的变体。归根结底,它仍然相当复杂,但感谢您的想法。
【参考方案1】:
部分和不完整解决方案如下所示,请参阅TODO cmets:
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
,但即使在阅读了大量关于它的文档之后,我还是无法理解它......
【讨论】:
【参考方案2】:您可以使用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)
它有效。
【讨论】:
@s-m-e,你能详细说明一下,我有什么遗漏的问题吗? 我想启动ssh
,以便用户可以以交互方式使用它。我的 Python 脚本在命令行中运行。 ssh
也是如此。 ssh
应该从 Python 进程继承命令行,因此用户实际上可以与ssh
交互。您的建议会启动 ssh
,但不会将控制权交给它。相反,它与我的 Python 脚本一起在后台消亡。以上是关于如何在 Windows 上有效地“替换”`os.execvpe` - 如果“子”进程是交互式命令行应用程序?的主要内容,如果未能解决你的问题,请参考以下文章
如何通过 SQL Server 插入的索引有效地替换长字符串?