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

Windows机器上,我试图从Python调用一个外部可执行文件并收集它的输出以进一步处理。 由于必须在调用可执行文件之前设置本地pathvariables,因此我创build了一个批处理脚本

  • 首先调用另一个脚本来设置%PATH%和
  • 然后使用给定的参数调用可执行文件。

* .bat文件如下所示:

@echo off call set_path.bat @echo on executable.exe %* 

和这样的Python代码:

 print("before call"); result = subprocess.check_output([batfile, parameters], stderr=subprocess.STDOUT, shell=True); print("after call"); print("------- ------- ------- printing result ------- ------- ------- "); print(result); print("------- ------- ------- /printing result ------- ------- ------- "); 

现在,从技术上讲,这是有效的。 可执行文件被调用的目标参数,运行,完成并产生结果。 我知道这一点,因为他们被嘲笑地显示在Python脚本运行的控制台中。

但是,结果string只包含批处理脚本返回的内容,而不包含可执行文件输出:

打电话之前

你好? 是的,这是executable.exe

打完电话后

——- ——- ——-打印结果——- ——- ——-

C:\ Users \ me \ Documents \ pythonscript \ execute \ executable.exe“para1 | para2 | para3”

——- ——- ——- /打印结果——- ——- ——-

subprocess.check_output命令本身以某种方式将预期的输出打印到控制台,它返回的内容只包含@echo再次打开后的batch file的输出。

我怎样才能访问和保存可执行文件的输出到一个string进一步工作?

或者我必须以某种方式修改batch file来捕获和打印输出,以便在check_output的结果中结束? 如果是这样,我怎么能这样做呢?

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

  • CreateConsoleScreenBuffer
  • GetConsoleScreenBufferInfoEx (最低支持的客户端:Windows Vista)
  • SetConsoleScreenBufferInfoEx (最低支持的客户端:Windows Vista)
  • SetConsoleWindowInfo
  • FillConsoleOutputCharacter
  • SetConsoleActiveScreenBuffer

确保请求GENERIC_READ | GENERIC_WRITE GENERIC_READ | GENERIC_WRITE访问时调用CreateConsoleScreenBuffer 。 您稍后需要读取访问权才能读取屏幕的内容。

特别是对于Python,使用ctypes来调用Windows控制台API中的函数。 另外,如果你通过msvcrt.open_osfhandle用C文件描述符来包装句柄,那么你可以将它作为msvcrt.open_osfhandlestdoutstderr参数传递。

屏幕缓冲区的文件描述符或句柄不能通过readReadFile甚至ReadConsole直接read 。 如果你有一个文件描述符,通过msvcrt.get_osfhandle获取底层句柄。 给定一个屏幕缓冲区句柄,调用ReadConsoleOutputCharacter从屏幕读取。 下面示例代码中的read_screen函数演示了从屏幕缓冲区开始到光标位置的读取。

需要将一个进程连接到控制台才能使用控制台API。 为此,我已经包含一个简单的allocate_console上下文管理器来暂时打开一个控制台。 这在通常不连接到控制台的GUI应用程序中很有用。

下面的例子在Windows 7和10中用Python 2.7和3.5进行了测试。

ctypes的定义

 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.