Как эффективно заменить os.execvpe в Windows, если дочерний процесс является интерактивным приложением командной строки?

Я имею дело со сценарием Python, который после некоторой подготовительной работы запускает ssh. Мой скрипт на самом деле представляет собой небольшой инструмент CLI. В Unix-подобных системах в конце своего жизненного цикла сценарий Python заменяется клиентом ssh, поэтому теперь пользователь может напрямую взаимодействовать с ssh (т.е. запускать произвольные команды на удаленном компьютере и т. Д.):

os.execvpe('ssh', ['ssh', '-o', 'foo', 'user@host'], os.environ)

Положительный сюрприз и примечание на случай, если вам интересно: в Windows 10 действительно встроена собственная версия OpenSSH, поэтому на этой платформе есть команда ssh.

os.execvpe присутствует в стандартной библиотеке Python в Windows, но не заменяет исходный процесс (Python). Ситуация ... несколько сложная: 1, 2, 3. Итог: Windows не реализует соответствующую семантику POSIX для замены запущенного процесса.

Принято считать, что вместо этого следует использовать subprocess.Popen, эффективно создавая дочерний процесс. Я могу запустить дочерний элемент, чтобы родитель продолжал работать, ИЛИ я могу запустить дочерний элемент, пока родитель умирает (я думаю, что Windows поддерживает последний, как и Unix-подобные системы). В любом случае пользователь не может взаимодействовать с дочерним элементом в командной строке.

Предполагая, что я сохраняю родителя в живых, теперь мне нужно написать тонну кода для передачи пользовательского ввода-вывода дочернему / от ребенка через родителя, например , например,. Последнее включает в себя управление потоками и даже потоками, в зависимости от того, насколько хорошо он должен себя вести - много мест для потенциальных проблем и поломок в будущем. Я не люблю этого делать (если можно этого избежать).

Как я могу эффективно заменить os.execvpe в Windows в описанном сценарии?


РЕДАКТИРОВАТЬ (1): Части и части, которые могут иметь отношение ...

Я полагаю, это зависит от того, как правильно настроить объект STARTUPINFO перед его передачей в Popen. Фактически, командная строка может быть унаследована в Windows.


РЕДАКТИРОВАТЬ (2): частичное решение через pywin32 - ssh открывается во втором новом окне cmd, с которым можно взаимодействовать. Исходная оболочка с Python остается открытой, сам Python выходит:

from win32.Demos.winprocess import Process
from shlex import join
Process(join(['ssh', '-o', 'foo', 'user@host']))

person s-m-e    schedule 03.05.2021    source источник
comment
Вы можете проверить pexpect.readthedocs.io/en/stable.   -  person CristiFati    schedule 12.05.2021
comment
@CristiFati Обертывание ssh в expect - это вариант ручного прохождения всех пользовательских операций ввода-вывода. В конце концов, это все еще довольно сложно, но спасибо за идею.   -  person s-m-e    schedule 12.05.2021


Ответы (2)


Частичное и неполное решение выглядит следующим образом (см. Комментарии TODO:

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 окне, с которым можно взаимодействовать. Исходное окно cmd.exe с Python в нем остается открытым, сам Python закрывается, возвращая управление самому cmd.exe. Это можно использовать, хотя и непоследовательно и некрасиво.

Я предполагаю, что все сводится к правильной настройке win32process.STARTUPINFO, но даже после того, как я прочитал тонны документации по нему, я почему-то не понимаю этого ...

person s-m-e    schedule 12.05.2021

Вы можете использовать функцию 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)

И это работает.

person Elbek    schedule 06.05.2021
comment
Это явно не работает. - person s-m-e; 12.05.2021
comment
@ s-m-e, не могли бы вы уточнить, есть ли что-то, что я упустил из виду? - person Elbek; 12.05.2021
comment
Я хочу запустить ssh, чтобы пользователь мог интерактивно использовать его. Мой скрипт Python запускается в командной строке. Так делает ssh. ssh должен наследовать командную строку от процесса Python, чтобы пользователь мог фактически взаимодействовать с ssh. Ваше предложение запускает ssh, но не передает ему контроль. Вместо этого он умирает в фоновом режиме вместе с моим скриптом Python. - person s-m-e; 12.05.2021