首页 > 解决方案 > 将子进程 stdout 写入文件会截断为 4096 字节

问题描述

我正在尝试将标准输出写入文件以获取在子进程中运行的内容。我的问题是我的输出总是被截断为 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() 但这似乎并没有任何影响。如何将整个标准输出打印到文件中?

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 的建议编辑代码)

标签: pythonsubprocesspipepython-3.8

解决方案


这里没有管道;Popen只有通过它才能创建它们subprocess.PIPE

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

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

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

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

我试过添加 context.file_to_close.flush()

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

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

来自评论:

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

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

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

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


推荐阅读