问题描述
我知道 sys.stdout
是 Python object that wraps the output file handle,但我想知道这些文件句柄是否“同步”并且始终相同?
例如,说 sys.stdout.isatty()
为 True。我调用 GetStdHandle(-11)
(-11 是 Windows 上的 STDOUT),然后调用一些失败的 Windows 控制台 API,发现错误的 errno 为 6(句柄无效)。 AFAIK,这意味着该句柄不是有效的控制台句柄。在这种情况下,它们不会“同步”。换句话说,是否可以在 GetStdHandle
返回的 STDOUT 句柄未重定向的情况下重定向 sys.stdout
?我的代码使用 GetStdHandle
,所以最终我应该测试 errno 6,但如果我能依赖 sys.stdout.isatty
就好了。
这是一个示例(我目前无法访问 Windows 机器,但希望代码是正确的)。在重定向和不重定向的情况下运行(或通常在调用 subprocess.check_output
时运行。
import sys
from ctypes import WinError,wintypes
STDOUT = -11
ERROR_INVALID_HANDLE = 6
kernel32 = ctypes.WinDLL('kernel32',use_errno=True,use_last_error=True)
handle = kernel32.GetStdHandle(STDOUT)
# Assume we set argtypes/restype for all win api functions here
if handle == wintypes.HANDLE(-1).value:
raise WinError()
console_mode = wintypes.DWORD(0)
# We use GetConsoleMode here but it Could be any function that expects a
# valid console handle
retval = kernel32.GetConsoleMode(handle,ctypes.byref(console_mode))
# Are the following assertions always true?
if retval == 0:
errno = ctypes.get_last_error()
if errno == ERROR_INVALID_HANDLE:
print('Invalid handle')
assert not sys.stdout.isatty()
else:
# Another error happened
raise WinError()
else:
assert sys.stdout.isatty()
我试图搜索 cpython 源代码,但找不到任何可以证实或否认这一点的内容。也许对代码库更有经验的人可以为我指明正确的方向?
编辑:我知道 CONOUT$ + CreateFile
API。我对在重定向下获取输入或输出句柄不感兴趣,但对了解 Windows 控制台句柄 API 和 sys.stdout
之间的关系不感兴趣。
解决方法
是的,我可以在 C++ 中重现这个问题。
可以使用CreateFile获取控制台的输出句柄,然后在调用windows控制台apis时将该句柄作为参数使用。
CreateFile 函数使一个进程能够获得它的句柄 控制台的输入缓冲区和活动屏幕缓冲区,即使 STDIN 和 STDOUT 已被重定向。打开控制台输入的句柄 缓冲区,在调用 CreateFile 时指定 CONIN$ 值。指定 调用 CreateFile 以打开控制台句柄的 CONOUT$ 值 活动屏幕缓冲区。 CreateFile 使您能够指定读/写 访问它返回的句柄。
在 C++ 中,它看起来像这样,
HANDLE hConsole = CreateFile("CONOUT$",GENERIC_WRITE | GENERIC_READ,FILE_SHARE_READ | FILE_SHARE_WRITE,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
效果很好,您可以根据需要将其转换为python代码。
更新:
import sys
import ctypes
from ctypes import WinError,wintypes
STDOUT = -11
sys.stdout = open('test.txt','w')
kernel32 = ctypes.WinDLL('kernel32',use_errno=True,use_last_error=True)
handle = kernel32.GetStdHandle(STDOUT)
if handle == wintypes.HANDLE(-1).value:
raise WinError()
console_mode = wintypes.DWORD(0)
retval = kernel32.GetConsoleMode(handle,ctypes.byref(console_mode))
print(retval)
if sys.stdout.isatty():
print('You are running in a real terminal')
else:
print('You are being piped or redirected')
retval
返回 1。它们都将打印在 test.txt 中。
当您删除 sys.stdout = open('test.txt','w')
时。