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

时间:2013-11-22 22:30:24

标签: python subprocess io-redirection

我有一个 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?")

以下是我无法修改的儿童剧本

#!/bin/bash
./grandchild.sh &
echo "Child: I had a son!"
for (( i = 0; i < 10; i++ )); do
    echo "Child: Hi Dad, meet your grandson!"
    sleep 1
done
exit 0

这是一个吵闹的孙子,我无法修改

#!/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

3 个答案:

答案 0 :(得分:3)

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

这意味着如果Python的fd 1和2进入你的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流打开:

f = open(os.devnull, "w")

然后基础文件描述符为f.fileno()

proc = subprocess.Popen('./child.sh', stdout=f.fileno(), stderr=f.fileno())

在这种情况下,您无法从任何相关流程中获得任何输出。


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

管道实体,它位于操作系统内部,只是从一端(管道的#34;写入端和#34;)复制到另一端(&#34;读取端&#34;) 。您的Python进程可以控制读取端。您必须通常不直接调用操作系统read系统调用,但请参阅下面的读取操作,以从中收集输出。

一般情况下,如果你停止阅读你的阅读端,那么管道就会填满#34;并且在写入端尝试操作系统级write的任何进程都被阻止&#34;直到有权访问读取结束的人(再次是你)才能从中读取。

如果丢弃读取端,使管道无处转储其输出,则写入端开始返回EPIPE个错误并发送SIGPIPE信号到任何尝试操作系统级别的进程{ {1}}致电。当您调用操作系统级write系统调用时,会发生这种丢弃,假设您没有将描述符移交给某些其他进程。当您的流程退出时(同样的假设,也会)退出。

没有一种方便的方法可以将读取端连接到像close这样的无限数据接收器,至少在大多数类Unix系统中是这样(有一些特殊的时髦系统调用要做这种&#34;管道&#34;)。但是如果你打算杀死这个孩子并且愿意让它的孙子死于/dev/null信号,你可以简单地关闭描述符(或退出)并让筹码掉落到可能的位置。

通过将SIGPIPE设置为SIGPIPE或阻止SIG_IGN,儿童和孙子女可以保护自己免于死亡。信号掩码在SIGPIPE系统调用中继承,因此在某些情况下,您可以阻止exec为子项(但有些孩子会解锁信号)。

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

如果您希望子进程将其stderr输出发送到相同的管道,以便您可以读取其stdout及其stderr,只需将AF_UNIX添加到{{1}调用。如果您希望子进程将其stderr输出发送到单独的管道,请添加stderr=subprocess.STDOUT。但是,如果你选择后者,事情会变得有点棘手。

如上所述,为了防止儿童被阻止,您必须调用操作系统Popen()。如果只有一个管道,这很容易:

stderr=subprocess.PIPE
例如

,或者:

read

将一次读取一行管道(在Python内部进行模缓冲)。您可以根据需要阅读多行或几行。

但是,如果有两个管道,则必须阅读其中的任何一个&#34;完整&#34;。 Python的for line in proc.stdout: ... 模块定义了line = proc.stdout.readline() 函数来为您执行此操作:

subprocess

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

一般情况下,只使用一根烟斗要简单得多,只要你喜欢就读多少(但也要多),然后关闭烟斗:

read

这是否足够取决于您的具体问题。

答案 1 :(得分:3)

如果你不关心孙子孙女;你可以全部杀死他们:

#!/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)

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

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

#!/usr/bin/env python
import collections
import os
import signal
import threading
from subprocess import Popen, PIPE, STDOUT

def killall(proc):
    print "Dad: kill'em all!"
    os.killpg(proc.pid, signal.SIGKILL)
    proc.wait()

proc = Popen('./child.sh', stdout=PIPE, stderr=STDOUT, preexec_fn=os.setsid)
print("Dad: I have begotten a son!")

# kill in a second
hitman = threading.Timer(1, killall, [proc])
hitman.start()

# save last 200 lines of output
q = collections.deque(proc.stdout, maxlen=200)
hitman.cancel()
proc.wait()

# print collected output
print '*'*60
print ''.join(q).decode('ascii'),
print '*'*60

请参阅Stop reading process output in Python without hang?

答案 2 :(得分:2)

现在,您的子进程可以通过STDOUT和STDERR与您的终端通信。相反,你可以像这样从子进程中劫持这些数据:

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

这会将您孩子的所有STDERR输出重定向到正常的STDOUT频道,然后通过STDOUT将您孩子的正常PIPE输出重定向到您的python脚本。您现在可以使用PIPEline = process.stdout.readline()读取,print(line)可以获取单行输出。您可以使用{{1}}将其打印回STDOUT。

一旦你杀了你的儿子(喘气),停止子进程的所有输出。

有关子流程的更多信息,请参阅我之前的一个与此类似的答案:python subprocess.call output is not interleaved