Python中的dup、dup2、tmpfile和stdout
这是一个跟这里相关的后续问题。
我想要实现的目标
我希望能够暂时把标准输出(stdout)重定向到一个临时文件,同时Python仍然可以继续向标准输出打印。这需要以下几个步骤:
- 创建一个标准输出的副本(
new
) - 创建一个临时文件(
tmp
) - 把标准输出重定向到这个临时文件(
tmp
) - 告诉Python使用这个副本(
new
)作为标准输出 - 把临时文件(
tmp
)重定向回“真实”的标准输出 - 再告诉Python使用“真实”的标准输出
- 读取并关闭临时文件(
tmp
)
实现方法
我尝试用以下方式实现上述步骤:
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)
时,无法写入临时文件。
问题
- 首先,我想知道为什么我的脚本没有按照我想要的方式工作。也就是说,为什么在脚本的前半部分只能写入临时文件?
- 我对
dup
和dup2
的用法还有点困惑。虽然我觉得我理解了如何将标准输出重定向到临时文件,但我完全不知道为什么os.dup2(new, 1)
会这样做。也许答案可以详细解释一下我脚本中所有的dup
和dup2
在做什么^^
1 个回答
15
你遇到“坏文件描述符”的原因是垃圾回收器会帮你关闭标准输出的文件描述符。看看这两行代码:
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
当执行第二行代码时,第一行创建的文件对象的引用计数就变成零了,这时垃圾回收器会把它销毁。文件对象在被销毁时会关闭它关联的文件描述符,而这个文件描述符恰好是1,也就是标准输出。所以你在使用os.fdopen
创建的对象时,得特别小心怎么去销毁它们。
下面是一个小例子来展示这个问题。os.fstat
只是用作示例的一个函数,当你传入一个已经关闭的文件描述符时,它会触发“坏文件描述符”的错误。
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()))