DUP, DUP2, TMPFILE и STDOUT в Python
-
27-10-2019 - |
Вопрос
Это следующий вопрос здесь.
Куда я хочу пойти
Я хотел бы иметь возможность временно перенаправить Stdout в временный файл, в то время как Python все еще может печатать в Stdout. Это будет включать следующие шаги:
- Создайте копию Stdout (
new
) - Создайте временный файл (
tmp
) - Перенаправить Stdout в
tmp
- Скажите Python использовать
new
как stdout - Перенаправить
tmp
в «настоящий» stdout - Скажите Python снова использовать «настоящий» Stdout
- Читать и закрыть
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
. Анкет Хотя я думаю, что понимаю, как перенаправление stdout в временный файл работает, я полностью знаю, почемуos.dup2(new, 1)
делает то, что делает. Может быть, ответ может подробно рассказать о том, что всеdup
а такжеdup2
S в моем сценарии делает ^^
Решение
Причина, по которой вы получаете «дескриптор плохого файла», заключается в том, что сборщик мусора закрывает для вас 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
просто используется в качестве примера функции, которая запускает ошибку «Descriptor File Descriptor», когда вы передаете ее закрытый FD.
import os
whatever = os.fdopen(1, 'w', 0)
os.fstat(1)
del whatever
os.fstat(1)
На самом деле у меня есть менеджер контекста, который, как мне кажется, делает именно (или почти по крайней мере, в моем случае, когда я случаюсь, нужен названный Tempfile), что вы ищете. Вы можете видеть, что он повторно использует исходный объект 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()))