更改正在运行的进程的输出重定向 [英] Change Output Redirection of Running Process

查看:301
本文介绍了更改正在运行的进程的输出重定向的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个 Python脚本,该脚本启动(启动孙子),一段时间后,我终止了子子,但是孙子孙女们继续往标准输出.杀死孩子之后,我想压制/重定向孙辈(及其所有后代)的stdout和stderr.

I have a parent Python script that launches a child (which launches grandchildren), and after some time, I terminate the child, but the grandchildren continue to pump to stdout. After I kill the child, I want to suppress/redirect the stdout and stderr of the grandchildren (and all their descendants).

这是父母:

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

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

Here is a noisy grandchild which I cannot modify.

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

在杀死孩子之前,我尝试这样做:

I tried doing this right before killing the child:

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

但是它似乎不起作用.输出为:

But it doesn't seem to work. The output is:

> ./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

推荐答案

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

When you invoke subprocess.Popen you can tell it to redirect stdout and/or stderr. If you don't, it leaves them un-redirected by allowing the OS to copy from the Python process's actual STDOUT_FILENO and STDERR_FILENO (which are fixed constants, 1 and 2).

这意味着,如果Python的fd 1和2将进入您的tty会话(例如,可能在诸如/dev/pts/0之类的基础设备上),则子级(因此也就是孙子级)正在直接讲话到同一会话(相同的/dev/pts/0).您在Python流程本身中所做的任何事情都不能改变这一点:这些流程是独立的流程,可以独立,直接地访问会话.

This means that if Python's fd 1 and 2 are going to your tty session (perhaps on an underlying device like /dev/pts/0 for instance), the child—and with this case, consequently, the grandchild as well—are talking directly to the same session (the same /dev/pts/0). Nothing you do in the Python process itself can change this: those are independent processes with independent, direct access to the session.

您可以做的是在适当的重定向下调用./child.sh:

What you can do is invoke ./child.sh with redirection in place:

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


快速旁注如果要丢弃子代及其子代的所有 all 输出,请打开os.devnull(如您所做的那样,或者使用os.open()来获取原始整数)文件描述符),然后将stdout和stderr连接到基础文件描述符.如果您已将其作为Python流打开:


Quick side-note edit: if you want to discard all output from the child and its grandchildren, open os.devnull (either as you did, or with os.open() to get a raw integer file descriptor) and connect stdout and stderr to the underlying file descriptor. If you have opened it as a Python stream:

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

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

then the underlying file descriptor is f.fileno():

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

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

In this case you cannot get any output from any of the processes involved.

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

Now file descriptor 1 in the child is connected to a pipe-entity, rather than directly to the session. (Since there is no stderr= above, fd 2 in the child is still connected directly to the session.)

位于操作系统内部的管道实体只是从一端(管道的写端")复制到另一端(读取端").您的Python进程可以控制读取端.您必须在该读取端调用OS read系统调用(通常不是直接调用,而是请参阅下文),以从该调用中收集输出.

The pipe-entity, which lives inside the operating system, simply copies from one end (the "write end" of the pipe) to the other (the "read end"). Your Python process has control of the read-end. You must invoke the OS read system call—often not directly, but see below—on that read end, to collect the output from it.

通常,如果您停止从读取端进行读取,则管道填满",并且在写入端尝试进行OS级write的任何进程都会被阻塞",直到有人有权访问读取端(再次是您)从中读取内容.

In general, if you stop reading from your read-end, the pipe "fills up" and any process attempting an OS-level write on the write-end is "blocked" until someone with access to the read end (that's you, again) reads from it.

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

If you discard the read-end, leaving the pipe with nowhere to dump its output, the write end starts returning EPIPE errors and sending SIGPIPE signals, to any process attempting an OS-level write call. This kind of discard occurs when you call the OS-level close system call, assuming you have not handed the descriptor off to some other process(es). It also occurs when your process exits (under the same assumption, again).

至少在大多数类Unix系统中,没有一种方便的方法可以将读取端连接到无限数据接收器(如/dev/null)(有一些带有一些特殊的时髦系统调用来执行此操作)的水暖").但是,如果您打算杀死孩子并愿意让其子孙死于SIGPIPE信号,则可以简单地关闭描述符(或退出)并使芯片落在可能的位置.

There is no convenient method by which you can connect the read-end to an infinite data sink like /dev/null, at least in most Unix-like systems (there are a few with some special funky system calls to do this kind of "plumbing"). But if you plan to kill the child and are willing to let its grandchildren die from SIGPIPE signals, you can simply close the descriptor (or exit) and let the chips fall where they may.

通过将SIGPIPE设置为SIG_IGN或阻止SIGPIPE,子孙可以保护自己免于死亡.信号掩码是在exec系统调用中继承的,因此在某些情况下,您可以为孩子阻止SIGPIPE(但是某些孩子会取消阻止信号).

Children and grandchildren can protect themselves from dying by setting SIGPIPE to SIG_IGN, or by blocking SIGPIPE. Signal masks are inherited across exec system calls so in some cases, you can block SIGPIPE for children (but some children will unblock signals).

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

If closing the descriptor is not suitable, you can create a new process that simply reads and discards incoming pipe data. If you use the fork system call, this is trivial. Alternatively some Unix-like systems allow you to pass file descriptors through AF_UNIX sockets to otherwise-unrelated (parent/child-wise) processes, so you could have a daemon that does this, reachable via an AF_UNIX socket. (This is nontrivial to code.)

如果希望子进程将其stderr输出发送到 same 管道,以便可以读取其stdout和stderr,只需将stderr=subprocess.STDOUT添加到Popen()调用中即可.如果希望子进程将其stderr输出发送到单独管道,请添加stderr=subprocess.PIPE.但是,如果您选择后者,则可能会有些棘手.

If you wish the child process to send its stderr output to the same pipe, so that you can read both its stdout and its stderr, simply add stderr=subprocess.STDOUT to the Popen() call. If you wish the child process to send its stderr output to a separate pipe, add stderr=subprocess.PIPE. If you do the latter, however, things can get a bit tricky.

要防止子代阻塞,如上所述,必须调用OS read调用.如果只有一个管道,这很容易:

To prevent children from blocking, as noted above, you must invoke the OS read call. If there is only one pipe this is easy:

for line in proc.stdout:
    ...

例如,或:

line = proc.stdout.readline()

将一次读取管道的每一行(Python内的模缓冲).您可以阅读任意多行.

will read the pipe one line at a time (modulo buffering inside Python). You can read as many or as few lines as you like.

但是,如果有两个管道,则您必须阅读管道已满"的管道. Python的subprocess模块定义了communicate()函数来为您执行此操作:

If there are two pipes, though, you must read whichever one(s) is/are "full". Python's subprocess module defines the communicate() function to do this for you:

stdout, stderr = proc.communicate()

此处的缺点是communicate()读取至完成:它需要获取 all 输出,该输出可以到达每个管道的写入端.这意味着它将反复调用OS级别的read操作,直到read指示数据结束.只有在某个时候对相应管道的写端具有写访问权的所有进程都已关闭管道的那端时,才会发生这种情况.换句话说,它等待子代和所有子代close连接到管道写端的描述符.

The drawback here is that communicate() reads to completion: it needs to get all output that can go to the write end of each pipe. This means it repeatedly calls the OS-level read operation until read indicates end-of-data. That occurs only when all processes that had, at some point, write access to the write end of the corresponding pipe, have closed that end of the pipe. In other words, it waits for the child and any grandchildren to close the descriptors connected to the write end of the pipe(s).

通常,只使用一个管道,读取任意数量(但仅读取尽可能多),然后简单地关闭管道,则要简单得多:

In general it's much simpler to use only one pipe, read as much (but only as much) as you like, then simply close the pipe:

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()

是否足够取决于您的特定问题.

Whether this suffices depends on your particular problem.

这篇关于更改正在运行的进程的输出重定向的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆