dup, dup2, tmpfile и stdout в python - PullRequest
       100

dup, dup2, tmpfile и stdout в python

3 голосов
/ 11 января 2012

Это следующий вопрос из здесь .


Куда я хочу пойти

Я хотел бы иметь возможность временно перенаправить стандартный вывод ввременный файл, в то время как Python все еще может печатать на стандартный вывод.Это может включать следующие шаги:

  1. Создать копию стандартного вывода (new)
  2. Создать временный файл (tmp)
  3. Перенаправить стандартный вывод вtmp
  4. Скажите Python использовать new в качестве стандартного вывода
  5. Перенаправить tmp в "настоящий" стандартный вывод
  6. Сказать Python снова использовать "настоящий" стандартный вывод
  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

, в то время как 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. Я все еще немного озадачен использованием dup и dup2.Хотя я думаю, что понимаю, как работает перенаправление stdout во временный файл, я полностью знаю, почему os.dup2(new, 1) делает то, что делает.Может быть, ответ мог бы объяснить, что делают все dup и dup2 в моем сценарии ^^

1 Ответ

11 голосов
/ 11 января 2012

Причина, по которой вы получаете «плохой дескриптор файла», заключается в том, что сборщик мусора закрывает стандартный вывод 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 просто используется в качестве примера функции, которая вызывает ошибку «Неверный дескриптор файла», когда вы передаете ему закрытый файл.

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