python 中的 dup、dup2、tmpfile 和 stdout

发布于 2024-12-26 01:44:08 字数 2785 浏览 0 评论 0原文

这是来自此处的后续问题。


我想要去的地方

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

  1. 创建 stdout 的副本 (new)
  2. 创建临时文件 (tmp)
  3. 将 stdout 重定向到 tmp
  4. 告诉 python使用 new 作为 stdout
  5. tmp 重定向到“真实”stdout
  6. 告诉 python 再次使用“真实”stdout
  7. 读取并关闭 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

while 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

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

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

问题

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

This is a follow up question from here.


Where I want do go

I would like to be able to temporarily redirect the stdout into a temp file, while python still is able to print to stdout. This would involve the following steps:

  1. Create a copy of stdout (new)
  2. Create a temp file (tmp)
  3. Redirect stdout into tmp
  4. Tell python to use new as stdout
  5. Redirect tmp into the "real" stdout
  6. Tell python to use the "real" stdout again
  7. Read and close tmp

Implementation

I tried to implement the above in the following way:

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

I would like to take a little break here to summarize.
The output to console up until here should read:

0.1
0.2
print

while sil should look like this: ['0.3\n']. So everything is working like a charm up until here. However, if I redo the script above again like so:

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

an error occurs and the output looks like this:

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

while sil reads: ['0.3\n', ''].

In other words: the second Func("1.3", True) is not able to write to the temp file.

Questions

  1. First of all, I would like to know why my script is not working like I want it to work. Meaning, why is it only possible in the first half of the script to write to the temp file?
  2. I am still a little puzzled by the usage of dup and dup2. While I think I understand how the redirection of stdout into a temp file is working I totally do now know why os.dup2(new, 1) is doing what it is doing. Maybe the answer could elaborate on what all the dup and dup2s in my script are doing^^

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

北座城市 2025-01-02 01:44:08

你得到“坏文件描述符”的原因是垃圾收集器为你关闭了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()))

The reason you get a "bad file descriptor" is that the garbage collector closes the stdout FD for you. Consider these two lines:

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

Now when the second of those two are executed the first file object's reference count drops to zero and the garbage collector destroys it. File objects close their associated fd when destructed, and that fd happens to be 1 = stdout. So you need to be very careful with how you destroy objects created with os.fdopen.

Here is a small example to show the problem. os.fstat is just used as an example function that triggers the "Bad file descriptor" error when you pass it an closed fd.

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

I actually happen to have a context manager that I think does exactly (or almost atleast, in my case I happen need a named tempfile) what you are looking for. You can see that it reuses the original sys.stdout object to avoid the close problematic.

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()))
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文