如何在 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 Wrapping ssh 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.Popensubprocess.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 插入的索引有效地替换长字符串?

在 pandas 中有效地使用替换

如何有效地关闭 MongoDB changeStream?

多线程环境中的扩展 OVERLAPPED 对象池:在何处以及如何有效地使用锁定

如何有效地分析 cygwin 应用程序?

如何在 BizTalk 管道组件中有效地修改流中的文本?