python中的dup,dup2,tmpfile和stdout

时间:2012-01-11 10:50:21

标签: python stdout io-redirection dup2 dup

这是来自here的后续问题。


我想去的地方

我希望能够暂时将stdout重定向到临时文件,而python仍然可以打印到stdout。这将涉及以下步骤:

  1. 创建stdout(new
  2. 的副本
  3. 创建临时文件(tmp
  4. 将标准输出重定向到tmp
  5. 告诉python使用new作为stdout
  6. tmp重定向到“真正的”标准输出
  7. 告诉python再次使用“真正的”标准输出
  8. 阅读并关闭tmp
  9. 实施

    我试图通过以下方式实现上述内容:

    import os
    import subprocess
    import sys
    
    #A function that calls an external process to print to stdout as well as
    #a python print to pythons stdout.
    def Func(s, p = False):
        subprocess.call('echo "{0}"'.format(s), shell = True)
        if p:
            print "print"
    
    sil = list() # <-- Some list to store the content of the temp files
    
    print "0.1" # Some testing of the
    Func("0.2") # functionality
    
    new = os.dup(1)    # Create a copy of stdout (new)
    tmp = os.tmpfile() # Create a temp file (tmp)
    
    os.dup2(tmp.fileno(), 1)            # Redirect stdout into tmp
    sys.stdout = os.fdopen(new, 'w', 0) # Tell python to use new as stdout
    
    Func("0.3", True) # <--- This should print "0.3" to the temp file and "print" to stdout
    
    os.dup2(new, 1)                   # Redirect tmp into "real" stdout
    sys.stdout = os.fdopen(1, 'w', 0) # Tell python to use "real" stdout again
    
    # Read and close tmp
    tmp.flush()
    tmp.seek(0, os.SEEK_SET)
    sil.append(tmp.read())
    tmp.close()
    

    我想在这里稍作休息来总结一下 到此为止的控制台输出应为:

    0.1
    0.2
    print
    

    虽然sil应如下所示:['0.3\n']。所以一切都像一个魅力,直到这里。但是,如果我再次重做上面的脚本:

    print "1.1" # Some testing of the
    Func("1.2") # functionality
    
    new = os.dup(1)    # Create a copy of stdout (new)
    tmp = os.tmpfile() # Create a temp file (tmp)
    
    os.dup2(tmp.fileno(), 1)            # Redirect stdout into tmp
    sys.stdout = os.fdopen(new, 'w', 0) # Tell python to use new as stdout
    
    # This should print "0.3" to the temp file and "print" to stdout and is the crucial point!
    Func("1.3", True) 
    
    os.dup2(new, 1)                   # Redirect tmp into "real" stdout
    sys.stdout = os.fdopen(1, 'w', 0) # Tell python to use "real" stdout again
    
    # Read and close tmp
    tmp.flush()
    tmp.seek(0, os.SEEK_SET)
    sil.append(tmp.read())
    

    发生错误,输出如下所示:

    1.1
    1.2
    /bin/sh: line 0: echo: write error: Bad file descriptor
    print
    

    sil读取:['0.3\n', '']

    换句话说:第二个Func("1.3", True)无法写入临时文件。

    问题

    1. 首先,我想知道为什么我的脚本不能像我希望它一样工作。意思是,为什么只能在脚本的前半部分写入临时文件?
    2. 我仍然对dupdup2的使用感到有些困惑。虽然我想我明白了如何将stdout重定向到临时文件中,但我现在完全知道为什么os.dup2(new, 1)正在做它正在做的事情。也许答案可以详细说明我的脚本中的所有dupdup2正在做什么^^

1 个答案:

答案 0 :(得分:11)

您获得“错误文件描述符”的原因是垃圾收集器为您关闭stdout FD。请考虑以下两行:

sys.stdout = os.fdopen(1, 'w', 0)    # from first part of your script
...
sys.stdout = os.fdopen(new, 'w', 0)  # from second part of your script

现在,当执行这两个中的第二个时,第一个文件对象的引用计数降为零,垃圾收集器将其销毁。文件对象在被破坏时关闭其关联的fd,并且fd恰好是1 = stdout。因此,您需要非常小心如何销毁使用os.fdopen创建的对象。

这是一个显示问题的小例子。 os.fstat仅用作示例函数,当您将其传递给已关闭的fd时会触发“错误的文件描述符”错误。

import os
whatever = os.fdopen(1, 'w', 0)
os.fstat(1)
del whatever
os.fstat(1)

我实际上碰巧有一个上下文管理器,我认为完全(或几乎至少,在我的情况下,我发生需要一个命名的临时文件)你正在寻找什么。您可以看到它重用原始的sys.stdout对象以避免出现问题。

import sys
import tempfile
import os

class captured_stdout:
    def __init__(self):
        self.prevfd = None
        self.prev = None

    def __enter__(self):
        F = tempfile.NamedTemporaryFile()
        self.prevfd = os.dup(sys.stdout.fileno())
        os.dup2(F.fileno(), sys.stdout.fileno())
        self.prev = sys.stdout
        sys.stdout = os.fdopen(self.prevfd, "w")
        return F

    def __exit__(self, exc_type, exc_value, traceback):
        os.dup2(self.prevfd, self.prev.fileno())
        sys.stdout = self.prev

## 
## Example usage
##

## here is a hack to print directly to stdout
import ctypes
libc=ctypes.LibraryLoader(ctypes.CDLL).LoadLibrary("libc.so.6")
def directfdprint(s):
    libc.write(1, s, len(s))


print("I'm printing from python before capture")
directfdprint("I'm printing from libc before captrue\n")

with captured_stdout() as E:
    print("I'm printing from python in capture")
    directfdprint("I'm printing from libc in capture\n")

print("I'm printing from python after capture")
directfdprint("I'm printing from libc after captrue\n")

print("Capture contains: " + repr(file(E.name).read()))