【问题标题】:Writing subprocess stdout to a file truncates to 4096 bytes将子进程标准输出写入文件会截断为 4096 字节
【发布时间】:2021-12-17 05:29:30
【问题描述】:

我正在尝试将标准输出写入文件以获取在子进程中运行的内容。我的问题是我的输出总是被截断为 4096 字节(一页大小),我认为这是由于 x86 系统上的 PIPE 大小造成的?

我这样设置我的子流程

with open("newfile.txt", "w") as f:
    proc = subprocess.Popen([find_executable(bin_name), *extra_args],
                             stdout=f, stderr=subprocess.DEVNULL)

让它在做其他事情的同时运行,然后

def shutdown(process):
    if process.poll() is None:
        try:
            process.terminate()
            process.wait(timeout.PROCESS_QUIT)
        except subprocess.TimeoutExpired:
            print(f"Process {process.args} not terminating after {timeout.PROCESS_QUIT} sec.",
                  file=sys.stderr)
            process.kill()

newfile.txt 中的输出总是精确地打印 4096 字节然后截断(通常是中间行)

我尝试过定期添加 context.file_to_close.flush() 但这似乎并没有任何影响。如何将整个 stdout 打印到文件中?

strace 给了我 7000 多行的输出,我不太确定如何阅读,但这似乎是与 newfile.txt 相关的所有内容?

openat(AT_FDCWD, "newfile.txt", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 6
fstat(6, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
ioctl(6, TCGETS, 0x7ffc05c3fac0)        = -1 ENOTTY (Inappropriate ioctl for device)
lseek(6, 0, SEEK_CUR)                   = 0
ioctl(6, TCGETS, 0x7ffc05c3f9c0)        = -1 ENOTTY (Inappropriate ioctl for device)
lseek(6, 0, SEEK_CUR)                   = 0
stat("/basepath/project/build-behave/the_app.app", {st_mode=S_IFREG|0755, st_size=5355744, ...}) = 0
openat(AT_FDCWD, "/dev/null", O_RDWR|O_CLOEXEC) = 7
pipe2([8, 9], O_CLOEXEC)                = 0
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fb12fc44a10) = 1349
close(9)                                = 0
close(7)                                = 0
read(8, "", 50000)                      = 0
close(8)                                = 0
close(6)                                = 0

(根据 mata 和 Charles Duffy 的建议编辑代码)

【问题讨论】:

  • 应该与管道尺寸无关。文件句柄直接传递给启动的进程,并写入那里。应该不需要在父进程中刷新它。您还对父级中的文件做什么?
  • 没有别的了。我打开文件,在 Popen 时将其提供给子进程,将文件句柄存储在行为测试上下文中,然后在终止子进程后关闭它
  • 我假设存在管道大小问题,因为我最初将 stdout 写入 subprocess.PIPE 并且由于此问题 bugs.python.org/issue41586 而上限为 4096。写入文件是我试图绕过这个限制
  • 该进程是否向 stderr 写入了很多内容,您正在阅读吗?如果您在进程运行时没有使用它,您的问题可能是进程在写入该管道时阻塞。如果您不阅读它,请将其发送到 subprocess.DEVNULL。而且似乎没有理由在上下文中保持文件打开,您应该在启动子进程后立即关闭它(它不会在那里关闭)。
  • 啊,好的,很高兴知道,谢谢!我将我的标准错误重定向到 subprocess.DEVNULL 并在弹出后立即关闭了文件。 newfile.txt 似乎仍然在同一点被截断

标签: python subprocess pipe python-3.8


【解决方案1】:

这里没有管道; Popen 仅在您通过 subprocess.PIPE 时创建它们。

大多数编程语言都有一个标准库,可以将输出缓冲到文件中,以减少系统调用的数量(这很昂贵)。 4096 字节是缓冲区的常见默认大小。

如果进程正常退出,则缓冲区中剩余的所有数据都会刷新到文件中。

您在进程中调用terminate。在 Windows 上,这会调用 TerminateProcess,它会立即终止进程而不会发出警告。在 Unix-likes 上,它发送SIGTERM,可以通过刷新缓冲区等处理,但仅仅因为它可以处理并不意味着它会。

您需要解决阻止进程退出的问题,然后无条件地等待它自行退出,或者向它发出信号,以它可以理解的方式刷新其缓冲区。您执行这些操作的方式取决于您正在运行的进程。

我已经尝试添加 context.file_to_close.flush()

假设context.file_to_closef 相同,这不会做任何事情,因为它会刷新Python 进程中的缓冲区,而不是子进程中的缓冲区。

(顺便说一句,您可能不需要保存f 并稍后关闭它。您可能可以在Popen 返回后立即关闭它,因为此时子进程有自己的文件句柄。)

来自评论:

该进程是否向 stderr 写入了很多内容,您正在阅读吗?如果您在进程运行时没有使用它,您的问题可能是进程在写入该管道时阻塞。如果您不阅读,请将其发送到 subprocess.DEVNULL。

如果您通过stderr=subprocess.PIPE(就像您在问题的第一个版本中所做的那样),这确实可以阻止进程退出。

但是用stderr=subprocess.DEVNULL 替换它意味着您不会看到子进程正在打印的任何错误消息。可能会有一条消息解释为什么它没有退出。

最好完全省略stderr 参数,以便子进程继承您的stderr,至少用于测试。

【讨论】:

  • 谢谢本格!您说“向它发出信号以它可以理解的方式刷新其缓冲区”并且“ context.file_to_close ... 不会做任何事情,因为它刷新 Python 进程中的缓冲区,而不是子进程中的缓冲区” .那么,什么是正确的方式来指示子进程将其输出刷新到文件中呢?
  • @Indigo 这取决于子进程是什么。可能没有办法做到这一点。在 Windows 上,您可以尝试 send_signal(CTRL_C_EVENT)(有关详细信息,请参阅 subprocess 文档),但如果这不起作用,我不会感到惊讶。最好的办法是找出子进程没有正常退出的原因。
猜你喜欢
  • 2015-08-24
  • 1970-01-01
  • 2023-04-06
  • 1970-01-01
  • 1970-01-01
  • 2013-02-16
  • 2013-04-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多