更改正在运行的进程的输出重定向

2024-04-19 04:33:49 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一个Python脚本,它启动一个子对象(它启动子对象),过了一段时间后,我终止了子进程,但子进程继续向stdout发送。在我杀死这个孩子之后,我想抑制/重定向孙辈(及其所有后代)的stdout和stderr。在

这是家长:

import time
import subprocess
proc = subprocess.Popen('./child.sh')
print("Dad: I have begotten a son!")
time.sleep(1)
proc.kill()
proc.wait()
print("Dad: My son hath died!")
time.sleep(2)
print("Dad: Why does my grandson still speak?")

这是我无法修改的子脚本。在

^{pr2}$

这是一个嘈杂的孙子,我无法修改它。在

#!/bin/bash
for (( i = 0; i < 10; i++ )); do
    echo "Grandchild: Wahhh"
    sleep 1
done
exit 0

我在杀死孩子之前就试着这么做:

import os
f = open(os.devnull,"w")
proc.stdout = proc.stderr = f

但似乎行不通。输出为:

> ./parent.py
Dad: I have begotten a son!
Child: I had a son!
Child: Hi Dad, meet your grandson!
Grandchild: Wahhh
Dad: My son hath died!
Grandchild: Wahhh
Grandchild: Wahhh
Dad: My grandson still speaketh!
Grandchild: Wahhh
Grandchild: Wahhh
Grandchild: Wahhh
Grandchild: Wahhh
Grandchild: Wahhh
Grandchild: Wahhh
Grandchild: Wahhh

Tags: 对象import脚本time进程mystdoutsleep
3条回答

当您调用subprocess.Popen时,您可以告诉它重定向stdout和/或{}。如果不这样做,则允许操作系统从Python进程的实际STDOUT_FILENO和{}(它们是固定常数,1和2)进行复制,从而使它们不被重定向。在

这意味着,如果Python的fd1和fd2要去tty会话(例如在底层设备上,比如/dev/pts/0),那么子进程和子进程(在这种情况下)直接与同一会话(同一个/dev/pts/0)对话。在Python进程本身中所做的任何事情都不能改变这一点:这些进程是独立的、直接访问会话的进程。在

您可以执行的操作是在重定向到位的情况下调用./child.sh

proc = subprocess.Popen('./child.sh', stdout=subprocess.PIPE)

快速边注编辑:如果您想放弃来自子级及其子级的所有输出,请打开os.devnull(如您所做的那样,或使用os.open()获得原始整数文件描述符),并将stdout和stderr连接到基础文件描述符。如果已将其作为Python流打开:

^{pr2}$

那么底层文件描述符是f.fileno()

^{3}$

在这种情况下,您无法从所涉及的任何进程中获得任何输出。在


现在子对象中的文件描述符1连接到管道实体,而不是直接连接到会话。(由于上面没有stderr=,子级中的fd2仍然直接连接到会话。)

pipe实体位于操作系统中,只需从管道的一端(“写入端”)复制到另一端(“读取端”)。Python进程可以控制读取端。您必须调用OSread系统调用,通常不是直接调用,而是在读取端参见下面的内容,以从中收集输出。在

一般来说,如果您停止从读端读取,管道将“填满”,并且任何试图在写入端执行操作系统级别write的进程都将被“阻止”,直到有权访问该读取端的人(又是您)从中读取。在

如果放弃读取端,使管道无处转储其输出,则写入端开始返回EPIPE错误,并向任何尝试操作系统级write调用的进程发送SIGPIPE信号。这种丢弃发生在调用OS-level close系统调用时,假设您没有将描述符交给其他进程。当进程退出时(同样的假设下),也会发生这种情况。在

没有一种方便的方法可以将读取端连接到无限大的数据接收器,比如/dev/null,至少在大多数类Unix的系统中(有一些系统调用了一些特殊的系统来完成这种“管道”)。但是如果您计划杀死这个孩子并且愿意让它的孙子们死于SIGPIPE信号,那么您可以简单地关闭描述符(或退出),让芯片落在可能的地方。在

通过将SIGPIPE设置为SIG_IGN,或通过阻止SIGPIPE来保护自己不致死亡。信号掩码是跨exec系统调用继承的,因此在某些情况下,您可以为子级阻止SIGPIPE(但某些子级将取消阻止信号)。在

如果不适合关闭描述符,则可以创建一个新进程,该进程只读取并丢弃传入的管道数据。如果您使用fork系统调用,这很简单。或者,一些类Unix的系统允许您通过AF_UNIX套接字将文件描述符传递给其他不相关的(父/子)进程,因此您可以有一个守护进程来执行此操作,并且可以通过AF_UNIX套接字进行访问。(这对于代码来说非常重要。)

如果希望子进程将其stderr输出发送到相同的管道,以便可以同时读取其stdout和stderr,只需将stderr=subprocess.STDOUT添加到Popen()调用中。如果希望子进程将其stderr输出发送到一个单独的管道,请添加stderr=subprocess.PIPE。但是,如果你做了后者,事情会变得有点棘手。在

在要防止子级阻塞,如上所述,必须调用OSread调用。如果只有一个管道,这很容易:

for line in proc.stdout:
    ...

例如,或者:

line = proc.stdout.readline()

将一次读取一行管道(Python中的模缓冲)。你可以读多少行就读多少行。在

如果有两个管道,则必须读取“满”的管道。Python的subprocess模块定义了communicate()函数来为您完成此操作:

stdout, stderr = proc.communicate()

这里的缺点是communicate()读取以完成:它需要获得所有输出,这些输出可以转到每个管道的写入端。这意味着它反复调用操作系统级别read操作,直到read指示数据结束。只有当在某个时刻对相应管道的写入端具有写访问权限的所有进程都关闭了该管道的该端时,才会发生这种情况。换句话说,它等待子对象和任何子对象close连接到管道写入端的描述符。在

一般来说,只使用一个管道要简单得多,读取多少(但只能读多少),然后简单地关闭管道:

proc = subprocess.Popen('./child.sh', stdout=subprocess.PIPE)
line1 = proc.stdout.readline()
line2 = proc.stdout.readline()
# that's all we care about
proc.stdout.close()
proc.kill()
status = proc.wait()

这是否足够取决于您的特定问题。在

现在,允许您的子进程通过STDOUT和STDERR与终端通信。相反,您可以从子进程中截取此数据,如下所示:

import subprocess
cmd = ['./child.sh']
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

这将孩子的所有STDERR输出重定向到正常STDOUT通道,然后通过PIPE将孩子的正常STDOUT输出重定向到python脚本。现在您可以使用PIPE读取PIPE,它只获取一行输出。您可以使用print(line)将其打印回STDOUT。在

一旦您杀死了您的儿子(gaps),停止子进程的所有输出。在

有关子进程的更多信息,请参阅我以前的一个答案,它类似于:python subprocess.call output is not interleaved

如果你不关心孙子孙女,你可以把他们都杀了:

#!/usr/bin/env python3
import os
import signal
import subprocess
import sys
import time

proc = subprocess.Popen('./child.sh', start_new_session=True)
print("Dad: I have begotten a son!")
time.sleep(1)
print("Dad: kill'em all!")
os.killpg(proc.pid, signal.SIGKILL)

for msg in "dead... silence... crickets... chirping...".split():
    time.sleep(1)
    print(msg, end=' ', flush=True)

您可以使用preexec_fn=os.setsid在旧的Python版本上模拟start_new_session=True。见Best way to kill all child processes。在

你可以在杀戮前收集儿童的输出:

^{pr2}$

Stop reading process output in Python without hang?

相关问题 更多 >