Python`tee'stdout的子进程

时间:2011-07-01 06:59:21

标签: python file-io

Python中是否有办法完成UNIX命令行tee的等效操作?我正在做一个典型的fork / exec模式,我希望来自子节点的stdout同时出现在日志文件和父节点的stdout上,而不需要任何缓冲。

例如,在这个python代码中,子节点的stdout最终出现在日志文件中,但不在父节点的stdout中。

pid = os.fork()
logFile = open(path,"w")
if pid == 0:
  os.dup2(logFile.fileno(),1)  
  os.execv(cmd)

编辑:我不想使用子进程模块。我正在对子进程做一些复杂的事情,需要我手动调用fork

5 个答案:

答案 0 :(得分:6)

在下文中,SOMEPATH是子可执行文件的路径,格式适合subprocess.Popen(请参阅其文档)。

import sys, subprocess


f = open('logfile.txt', 'w')
proc = subprocess.Popen(SOMEPATH, stdout=subprocess.PIPE)

while True:
    out = proc.stdout.read(1)
    if out == '' and proc.poll() != None:
        break
    if out != '':
        # CR workaround since chars are read one by one, and Windows interprets
        # both CR and LF as end of lines. Linux only has LF
        if out != '\r': f.write(out)
        sys.stdout.write(out)
        sys.stdout.flush()

答案 1 :(得分:6)

这里有一个不使用subprocess模块的工作解决方案。虽然,您可以将它用于tee进程,同时仍然使用exec*函数套件进行自定义子进程(只需使用stdin=subprocess.PIPE然后将描述符复制到stdout)。

import os, time, sys

pr, pw = os.pipe()
pid = os.fork()

if pid == 0:
    os.close(pw)
    os.dup2(pr, sys.stdin.fileno())
    os.close(pr)
    os.execv('/usr/bin/tee', ['tee', 'log.txt'])
else:
    os.close(pr)
    os.dup2(pw, sys.stdout.fileno())
    os.close(pw)

    pid2 = os.fork()

    if pid2 == 0:
        # Replace with your custom process call
        os.execv('/usr/bin/yes', ['yes'])
    else:
        try:
            while True:
                time.sleep(1)
        except KeyboardInterrupt:
            pass

请注意,tee命令在内部执行与Ben在其答案中建议的相同:读取输入并在写入时循环输出文件描述符。它可能更有效,因为优化的实现,因为它是用C编写的,但是你有不同管道的开销(不知道哪个解决方案更有效,但在我看来,重新分配自定义文件对象stdout是一个更优雅的解决方案。)

更多资源:

答案 2 :(得分:2)

像这样的方法会做你想要的吗?

import sys

class Log(object):
    def __init__(self, filename, mode, buffering):
        self.filename = filename
        self.mode = mode
        self.handle = open(filename, mode, buffering)

    def write(self, thing):
        self.handle.write(thing)
        sys.stdout.write(thing)

您可能需要为此实现更多file界面才能真正有用(如果您需要,我已经遗漏了modebuffering正确的默认值)。然后,您可以在子进程中将所有写入操作都写入Log实例。或者,如果你想成为一个神奇的人,并且你确定你实现了足够的file接口,事情不会摔倒并死亡,你可能会分配{{1成为这个类的一个实例。然后我任何写入stdout的方法,包括sys.stdout,都将通过日志类。

编辑添加:显然,如果您分配给print,则必须在sys.stdout方法中执行其他操作以将输出回显到标准输出!我认为您可以使用write

答案 3 :(得分:2)

哦,你。在看到你的例子的最后一行之前,我得到了一个不错的答案:execv()。好吧,大便。最初的想法是用这个博客帖子的tee类的实例替换每个子进程'stdout,并将流拆分为原始stdout和日志文件:

http://www.shallowsky.com/blog/programming/python-tee.html

但是,因为你正在使用execv(),所以子进程'tee实例会被破坏,所以这不起作用。

不幸的是,对于您而言,我找不到任何“开箱即用”的问题解决方案。最接近的是在子流程中生成实际的T恤程序;如果你想要更加跨平台,你可以派一个简单的Python代替。

首先要知道编写T恤替代品时:tee确实是一个简单的程序。在我见过的所有真正的C实现中,它并不比这复杂得多:

while((character = read()) != EOF) {
    /* Write to all of the output streams in here, then write to stdout. */
}

不幸的是,你不能只将两个流连接在一起。这将是非常有用的(因此一个流的输入将自动转发出另一个流),但我们没有这样的奢侈,没有自己编码。所以,Eli和我将有非常相似的答案。不同的是,在我的回答中,Python'tee'将通过管道在一个单独的进程中运行;那样,父线程仍然有用!

(请记住:复制博客文章的开球课程。)

import os, sys

# Open it for writing in binary mode.
logFile=open("bar", "bw")

# Verbose names, but I wanted to get the point across.
# These are file descriptors, i.e. integers.
parentSideOfPipe, childSideOfPipe = os.pipe()

# 'Tee' subprocess.
pid = os.fork()
if pid == 0:
    while True:
        char = os.read(parentSideOfPipe, 1)
        logFile.write(char)
        os.write(1, char)

# Actual command
pid = os.fork()
if pid == 0:
    os.dup2(childSideOfPipe, 1)
    os.execv(cmd)

如果那不是您想要的,我很抱歉,但这是我能找到的最佳解决方案。

祝你项目的其余部分好运!

答案 4 :(得分:0)

第一个明显的答案是分叉实际的T恤过程,但这可能并不理想。

tee代码(来自coreutils)只是读取每一行并依次写入每个文件(有效缓冲)。