【问题标题】:Expose Python standard IO to Subprocess将 Python 标准 IO 暴露给子进程
【发布时间】:2016-10-04 01:38:14
【问题描述】:

在 Python 3.5.1 上,我有以下内容:

output = subprocess.check_output(cmd).decode(encoding="UTF-8")

这会调用正确调用的命令cmdcmd 中的 C++14 代码如下所示:

HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
assert(handle!=INVALID_HANDLE_VALUE); //Always passes
assert(handle!=nullptr);              //Always passes

CONSOLE_SCREEN_BUFFER_INFO csbi;
BOOL result = GetConsoleScreenBufferInfo(handle,&csbi);
assert(result!=0); //Always fails.  `GetLastError()` returns 6 (invalid handle) 

运行上述 Python 代码会导致子进程 cmd 在指定行失败。根据 Python 文档,在这种情况下,stdout/stderr 应该从父进程(即 Python 解释器)继承。所以,不应该。事实上,上面的工作很好,例如printfed 输出。

尝试显式重定向也会失败:

#Traceback (most recent call last):
#  File "C:\dev\Python35\lib\subprocess.py", line 914, in __init__
#    errread, errwrite) = self._get_handles(stdin, stdout, stderr)
#  File "C:\dev\Python35\lib\subprocess.py", line 1145, in _get_handles
#    c2pwrite = msvcrt.get_osfhandle(stdout.fileno())
#io.UnsupportedOperation: fileno
p = subprocess.Popen(cmd.split(" "),stdout=sys.stdout,stderr=sys.stderr)

#Using `subprocess.PIPE` instead fails in the same way as the `subprocess.check_output(...)`
#example originally given above.

出了什么问题?我该如何解决?

【问题讨论】:

  • check_output 设置stdout=PIPE。您不能将该句柄与GetConsoleScreenBufferInfoWriteConsole 等控制台函数一起使用。 printf 有效,因为它最终调用WriteFile,它将字节写入任何类型的文件句柄。
  • sys.stdout 是 IDE 中的类文件对象吗?在这种情况下,它不是具有可以在子进程的标准句柄中继承的句柄的操作系统文件。如果您的 C++ 代码在 stdout 不是控制台时回退到使用 C 标准 I/O(最好是无缓冲行),那么您的 Python 代码可以使用线程从 p.stdout 管道中读取。 check_outputPopen.communicate 为您执行此操作,但您可能需要更具交互性的内容。
  • @HarryJohnston,我们知道sys.stdout 与Windows 文件(控制台、管道、磁盘)没有关联,因为它没有fileno()。我认为 imallett 希望子进程的标准输出显示在 Python 的 sys.stdout 上,无论 sys.stdout 是什么(例如,sys.stdout 可能是使用管道或套接字与 IDE 的交互式窗口对话的代理对象)。在这种情况下,线程可以循环 p.stdout.read(1)p.stdout.readline() 并写入 sys.stdout
  • @eryksun:没错。如果 OP 正在谈论我正在考虑的简单场景,那么对 .Popen 的调用应该可以正常工作。至少,它对我有用。 :-)

标签: python c++ windows subprocess stdio


【解决方案1】:

管道不是控制台。 check_output() 在内部使用 stdout=PIPE。它不会重定向控制台输出(由WriteConsoleW() 生成)。

第一个错误(无效句柄)表明标准输出不是控制台设备。

除非这是一种期望的行为(to print outside of the standard output);如果stdout is not a console device,则使用WriteFile()

第二个错误 (io.UnsupportedOperation: fileno) 表明 sys.stdout 不是真实文件,因此您不能将其作为 stdout 参数传递给子进程(您可以 redirect subprocess' stdout using stdout=PIPE and print the output using print() or sys.stdout.write() method directly)。

【讨论】:

  • 除了这个答案之外,经过一些外部阅读后,我认为我真正想要的是cmd 使用Python解释器打开的控制台。由于cmd 是一个子进程,我不确定这种愿望是否有意义。¶ 然而,当调用cmd 时,cmd 似乎打开了它自己的控制台窗口(运行时会出现一个新窗口)。您能否解释一下为什么cmd 无法识别此窗口并在例如 Python 调用中没有重定向时写入它?
  • @imallett:“C++ 应用程序如何使用由其父 Python 脚本打开的控制台(通过子进程)”听起来是个不错的问题,但这将是第三个问题(尝试将您的问题限制在每个问题一个问题)。你试过明显的吗? (不要重定向标准输出,只是不要传递stdout 参数,例如,使用subprocess.check_call(cmd)——在这种情况下,子进程应该打印到控制台)。此外,您的代码似乎假设标准输出是控制台(我最初错过了它 - 我已经相应地更新了答案)。
  • 我并不是要问我如何使用父母的控制台,因为这确实是一个单独的问题。我的意思是我不清楚我是如何失败的。¶ 尝试建议的 subprocess.check_call(cmd) 有效(断言通过,输出打印到控制台)。重定向时,它会崩溃。¶ 我想我理解:句柄是从标准输出句柄生成的。重定向时,这是一个管道,而不是定向到终端的文件。所以终端仍然存在,但是由于没有附加标准输出,所以获取句柄会返回管道,所以终端的事情失败了。是吗?
  • @imallett 是的。要点是正确的:“句柄是从标准输出句柄生成的。重定向时,这是一个管道,而不是指向终端的文件。”
  • @imallett,如果您知道您已连接到控制台(例如,GetConsoleCP 返回一个非零值),您可以打开\\.\CONOUT$ 对其进行写入。它应该以"r+" 访问权限打开。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-17
  • 2011-09-07
  • 1970-01-01
  • 2012-12-25
  • 1970-01-01
相关资源
最近更新 更多