DUP, dup2, tmpfile e stdout in pitone
-
27-10-2019 - |
Domanda
Questo è un follow-up domanda da qui .
Dove voglio andare do
Mi piacerebbe essere in grado di reindirizzare temporaneamente lo stdout in un file temporaneo, mentre il pitone è ancora in grado di stampare su stdout. Ciò comporterebbe le seguenti operazioni:
- Crea una copia di stdout (
new
) - Crea un file temporaneo (
tmp
) - reindirizzamento stdout in
tmp
- python dicono di usare
new
come stdout - Redirect
tmp
nel "reale" stdout - python dicono di usare il "vero" stdout di nuovo
- Leggi e vicino
tmp
Attuazione
Ho cercato di attuare quanto sopra in modo seguente:
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()
Vorrei fare una piccola pausa qui a riassumere.
L'uscita per console fino a qui dovrebbero essere:
0.1
0.2
print
mentre sil
dovrebbe assomigliare a questa: ['0.3\n']
. Quindi, tutto funziona come un fascino fino a qui. Tuttavia, se rifare lo script precedente di nuovo in questo modo:
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())
si verifica un errore e gli sguardi di output come questo:
1.1
1.2
/bin/sh: line 0: echo: write error: Bad file descriptor
print
mentre sil
si legge:. ['0.3\n', '']
In altre parole:. Il secondo Func("1.3", True)
non è in grado di scrivere il file temporaneo
Domande
- Prima di tutto, vorrei sapere perché il mio script non funziona come voglio farlo funzionare. Che significa, perché è possibile solo nella prima metà dello script di scrivere il file temporaneo?
- Sono ancora un po 'perplesso per l'utilizzo di
dup
edup2
. Mentre Credo di capire come il reindirizzamento di stdout in un file temporaneo sta lavorando Sono assolutamente fare ora so perchéos.dup2(new, 1)
sta facendo quello che sta facendo. Forse la risposta potrebbe approfondire quello che tutto ildup
edup2
s nel mio script stanno facendo ^^
Soluzione
Il motivo si ottiene un "descrittore di file cattivo" è che il garbage collector chiude lo stdout FD per voi. Consideriamo queste due righe:
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
Ora, quando vengono eseguite secondo di questi due conteggio di riferimento del primo oggetto file scende a zero e il garbage collector distrugge. oggetti file chiudono l'fd associato quando distrutto, e che fd sembra essere 1 = stdout. Quindi è necessario essere molto attenti a come si distrugge oggetti creati con os.fdopen
.
Ecco un piccolo esempio per mostrare il problema. os.fstat
è solo usato come una funzione di esempio che inneschi l'errore "Bad descrittore di file" quando si passa un fd chiuso.
import os
whatever = os.fdopen(1, 'w', 0)
os.fstat(1)
del whatever
os.fstat(1)
Io in realtà capita di avere un manager contesto che penso lo fa esattamente (o quasi atleast, nel mio caso mi capita bisogno di un tempfile di nome) quello che stai cercando. Si può vedere che riutilizza l'oggetto sys.stdout originale per evitare la chiusura problematico.
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()))