您好, 欢迎来到 !    登录 | 注册 | | 设为首页 | 收藏本站

通过subprocess.check_output调用的可执行文件在控制台上打印,但不返回结果

通过subprocess.check_output调用的可执行文件在控制台上打印,但不返回结果

如果程序直接写入控制台(例如,通过打开CONOUT$设备)而不是写入过程标准句柄,则唯一的选择是直接读取控制台屏幕缓冲区。为了简化此过程,请从新的空白屏幕缓冲区开始。通过以下功能创建,调整大小,初始化和激活新的屏幕缓冲区:

请确保GENERIC_READ | GENERIC_WRITE在致电时请求访问权限CreateConsoleScreenBuffer。您稍后需要读取权限才能读取屏幕内容

专门针对Python,使用ctypes调用Windows控制台API中的函数。另外,如果您通过C将文件句柄包装为C文件描述符msvcrt.open_osfhandle,则可以将其作为stdoutstderr参数传递subprocess.Popen

文件描述符或手柄屏幕缓冲区无法通过直接读取readReadFile或甚ReadConsole。如果您有文件描述符,请通过获取基本句柄msvcrt.get_osfhandle。给定屏幕缓冲区句柄,调用ReadConsoleOutputCharacter以从屏幕读取。read_screen下面的示例代码中的函数演示了从屏幕缓冲区的开始到光标位置的读取。

为了使用控制台API,需要将进程附加到控制台。为此,我提供了一个简单的allocate_console上下文管理器来临时打开控制台。这在GUI应用程序中很有用,而GUI应用程序通常未连接到控制台。

以下示例在Windows 7和10,Python 2.7和3.5中进行了测试。

import os
import contextlib
import msvcrt
import ctypes
from ctypes import wintypes

kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)

GENERIC_READ  = 0x80000000
GENERIC_WRITE = 0x40000000
FILE_SHARE_READ  = 1
FILE_SHARE_WRITE = 2
CONSOLE_TEXTMODE_BUFFER = 1
INVALID_HANDLE_VALUE = wintypes.HANDLE(-1).value
STD_OUTPUT_HANDLE = wintypes.DWORD(-11)
STD_ERROR_HANDLE = wintypes.DWORD(-12)

def _check_zero(result, func, args):
    if not result:
        raise ctypes.WinError(ctypes.get_last_error())
    return args

def _check_invalid(result, func, args):
    if result == INVALID_HANDLE_VALUE:
        raise ctypes.WinError(ctypes.get_last_error())
    return args

if not hasattr(wintypes, 'LPDWORD'): # Python 2
    wintypes.LPDWORD = ctypes.POINTER(wintypes.DWORD)
    wintypes.PSMALL_RECT = ctypes.POINTER(wintypes.SMALL_RECT)

class COORD(ctypes.Structure):
    _fields_ = (('X', wintypes.SHORT),
                ('Y', wintypes.SHORT))

class CONSOLE_SCREEN_BUFFER_INFOEX(ctypes.Structure):
    _fields_ = (('cbSize',               wintypes.ULONG),
                ('dwSize',               COORD),
                ('dwCursorPosition',     COORD),
                ('wAttributes',          wintypes.WORD),
                ('srWindow',             wintypes.SMALL_RECT),
                ('dwMaximumWindowSize',  COORD),
                ('wPopupAttributes',     wintypes.WORD),
                ('bFullscreenSupported', wintypes.BOOL),
                ('ColorTable',           wintypes.DWORD * 16))
    def __init__(self, *args, **kwds):
        super(CONSOLE_SCREEN_BUFFER_INFOEX, self).__init__(
                *args, **kwds)
        self.cbSize = ctypes.sizeof(self)

PCONSOLE_SCREEN_BUFFER_INFOEX = ctypes.POINTER(
                                    CONSOLE_SCREEN_BUFFER_INFOEX)
LPSecurity_ATTRIBUTES = wintypes.LPVOID

kernel32.GetStdHandle.errcheck = _check_invalid
kernel32.GetStdHandle.restype = wintypes.HANDLE
kernel32.GetStdHandle.argtypes = (
    wintypes.DWORD,) # _In_ nStdHandle

kernel32.CreateConsoleScreenBuffer.errcheck = _check_invalid
kernel32.CreateConsoleScreenBuffer.restype = wintypes.HANDLE
kernel32.CreateConsoleScreenBuffer.argtypes = (
    wintypes.DWORD,        # _In_       dwDesiredAccess
    wintypes.DWORD,        # _In_       dwShareMode
    LPSecurity_ATTRIBUTES, # _In_opt_   lpSecurityAttributes
    wintypes.DWORD,        # _In_       dwFlags
    wintypes.LPVOID)       # _Reserved_ lpScreenBufferData

kernel32.GetConsoleScreenBufferInfoEx.errcheck = _check_zero
kernel32.GetConsoleScreenBufferInfoEx.argtypes = (
    wintypes.HANDLE,               # _In_  hConsoleOutput
    PCONSOLE_SCREEN_BUFFER_INFOEX) # _Out_ lpConsoleScreenBufferInfo

kernel32.SetConsoleScreenBufferInfoEx.errcheck = _check_zero
kernel32.SetConsoleScreenBufferInfoEx.argtypes = (
    wintypes.HANDLE,               # _In_  hConsoleOutput
    PCONSOLE_SCREEN_BUFFER_INFOEX) # _In_  lpConsoleScreenBufferInfo

kernel32.SetConsoleWindowInfo.errcheck = _check_zero
kernel32.SetConsoleWindowInfo.argtypes = (
    wintypes.HANDLE,      # _In_ hConsoleOutput
    wintypes.BOOL,        # _In_ bAbsolute
    wintypes.PSMALL_RECT) # _In_ lpConsoleWindow

kernel32.FillConsoleOutputCharacterW.errcheck = _check_zero
kernel32.FillConsoleOutputCharacterW.argtypes = (
    wintypes.HANDLE,  # _In_  hConsoleOutput
    wintypes.WCHAR,   # _In_  cCharacter
    wintypes.DWORD,   # _In_  nLength
    COORD,            # _In_  dwWriteCoord
    wintypes.LPDWORD) # _Out_ lpNumberOfCharsWritten

kernel32.ReadConsoleOutputCharacterW.errcheck = _check_zero
kernel32.ReadConsoleOutputCharacterW.argtypes = (
    wintypes.HANDLE,  # _In_  hConsoleOutput
    wintypes.LPWSTR,  # _Out_ lpCharacter
    wintypes.DWORD,   # _In_  nLength
    COORD,            # _In_  dwReadCoord
    wintypes.LPDWORD) # _Out_ lpNumberOfCharsRead
@contextlib.contextmanager
def allocate_console():
    allocated = kernel32.AllocConsole()
    try:
        yield allocated
    finally:
        if allocated:
            kernel32.FreeConsole()

@contextlib.contextmanager
def console_screen(ncols=None, nrows=None):
    info = CONSOLE_SCREEN_BUFFER_INFOEX()
    new_info = CONSOLE_SCREEN_BUFFER_INFOEX()
    nwritten = (wintypes.DWORD * 1)()
    hStdOut = kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
    kernel32.GetConsoleScreenBufferInfoEx(
           hStdOut, ctypes.byref(info))
    if ncols is None:
        ncols = info.dwSize.X
    if nrows is None:
        nrows = info.dwSize.Y
    elif nrows > 9999:
        raise ValueError('nrows must be 9999 or less')
    fd_screen = None
    hScreen = kernel32.CreateConsoleScreenBuffer(
                GENERIC_READ | GENERIC_WRITE,
                FILE_SHARE_READ | FILE_SHARE_WRITE,
                None, CONSOLE_TEXTMODE_BUFFER, None)
    try:
        fd_screen = msvcrt.open_osfhandle(
                        hScreen, os.O_RDWR | os.O_BINARY)
        kernel32.GetConsoleScreenBufferInfoEx(
               hScreen, ctypes.byref(new_info))
        new_info.dwSize = COORD(ncols, nrows)
        new_info.srWindow = wintypes.SMALL_RECT(
                Left=0, Top=0, Right=(ncols - 1),
                Bottom=(info.srWindow.Bottom - info.srWindow.Top))
        kernel32.SetConsoleScreenBufferInfoEx(
                hScreen, ctypes.byref(new_info))
        kernel32.SetConsoleWindowInfo(hScreen, True,
                ctypes.byref(new_info.srWindow))
        kernel32.FillConsoleOutputCharacterW(
                hScreen, u'\0', ncols * nrows, COORD(0,0), nwritten)
        kernel32.SetConsoleActiveScreenBuffer(hScreen)
        try:
            yield fd_screen
        finally:
            kernel32.SetConsoleScreenBufferInfoEx(
                hStdOut, ctypes.byref(info))
            kernel32.SetConsoleWindowInfo(hStdOut, True,
                    ctypes.byref(info.srWindow))
            kernel32.SetConsoleActiveScreenBuffer(hStdOut)
    finally:
        if fd_screen is not None:
            os.close(fd_screen)
        else:
            kernel32.CloseHandle(hScreen)

def read_screen(fd):
    hScreen = msvcrt.get_osfhandle(fd)
    csbi = CONSOLE_SCREEN_BUFFER_INFOEX()
    kernel32.GetConsoleScreenBufferInfoEx(
        hScreen, ctypes.byref(csbi))
    ncols = csbi.dwSize.X
    pos = csbi.dwCursorPosition
    length = ncols * pos.Y + pos.X + 1
    buf = (ctypes.c_wchar * length)()
    n = (wintypes.DWORD * 1)()
    kernel32.ReadConsoleOutputCharacterW(
        hScreen, buf, length, COORD(0,0), n)
    lines = [buf[i:i+ncols].rstrip(u'\0')
                for i in range(0, n[0], ncols)]
    return u'\n'.join(lines)
if __name__ == '__main__':
    import io
    import textwrap
    import subprocess

    text = textwrap.dedent('''\
        Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
        eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
        enim ad minim veniam, quis nostrud exercitation ullamco laboris
        nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
        in reprehenderit in voluptate velit esse cillum dolore eu
        fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
        proident, sunt in culpa qui officia deserunt mollit anim id est
        laborum.''')

    cmd = ("python -c \""
           "print('piped output');"
           "conout = open(r'CONOUT$', 'w');"
           "conout.write('''%s''')\"" % text)

    with allocate_console() as allocated:
        with console_screen(nrows=1000) as fd_conout:
            stdout = subprocess.check_output(cmd).decode()
            conout = read_screen(fd_conout)
            with io.open('result.txt', 'w', encoding='utf-8') as f:
                f.write(u'stdout:\n' + stdout)
                f.write(u'\nconout:\n' + conout)
stdout:
piped output

conout:
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
in reprehenderit in voluptate velit esse cillum dolore eu
fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est
laborum.
其他 2022/1/1 18:31:48 有451人围观

撰写回答


你尚未登录,登录后可以

和开发者交流问题的细节

关注并接收问题和回答的更新提醒

参与内容的编辑和改进,让解决方法与时俱进

请先登录

推荐问题


联系我
置顶