dup, dup2, tmpfile et stdout en python
-
27-10-2019 - |
Question
Là où je veux aller do
Je voudrais être en mesure de rediriger temporairement le stdout dans un fichier temporaire, alors que python est encore capable d'imprimer à stdout. Cela implique les étapes suivantes:
- Créer une copie de stdout (
new
) - Créer un fichier temporaire (de
tmp
) - stdout dans
tmp
Redirect - python suggèrent d'utiliser
new
comme stdout - Redirect
tmp
dans le "réel" stdout - python suggèrent d'utiliser le stdout "vrai" nouveau
- Lire et à proximité
tmp
Mise en œuvre
J'ai essayé de mettre en œuvre ce qui précède la manière suivante:
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()
Je voudrais faire une petite pause ici pour résumer.
La sortie à la console jusqu'à ici doit se lire:
0.1
0.2
print
alors sil
devrait ressembler à ceci: ['0.3\n']
. Donc, tout fonctionne comme un haut charme jusqu'à ici. Cependant, si je refais le script ci-dessus à nouveau comme ceci:
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())
une erreur se produit et les regards de sortie comme ceci:
1.1
1.2
/bin/sh: line 0: echo: write error: Bad file descriptor
print
en sil
lit comme suit:. ['0.3\n', '']
En d'autres termes:. Le second Func("1.3", True)
ne peut pas écrire dans le fichier temp
Questions
- Tout d'abord, je voudrais savoir pourquoi ne fonctionne pas mon script comme je veux travailler. Ce qui signifie, pourquoi est-il possible que dans la première moitié du script pour écrire dans le fichier temporaire?
- Je suis encore un peu perplexe par l'utilisation de
dup
etdup2
. Bien que je pense que je comprends comment la redirection de stdout dans un fichier temporaire fonctionne totalement, je ne sais maintenant pourquoios.dup2(new, 1)
fait ce qu'il fait. Peut-être que la réponse pourrait élaborer sur ce que tous lesdup
etdup2
s dans mon script font ^^
La solution
La raison pour laquelle vous obtenez un « mauvais descripteur de fichier » est que le garbage collector ferme la FD stdout pour vous. Tenez compte de ces deux lignes:
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
Maintenant, quand la seconde de ces deux sont exécutés le compte de référence du premier objet de fichier tombe à zéro et le garbage collector détruit. objets de fichiers fermer leur fd associés lorsque destructed, et que fd se trouve être 1 = stdout. Donc, vous devez être très prudent avec la façon dont vous détruisez les objets créés avec os.fdopen
.
Voici un petit exemple pour montrer le problème. os.fstat
est simplement utilisé comme un exemple de fonction qui déclenche l'erreur « descripteur fichier incorrect » lorsque vous passez un fd fermé.
import os
whatever = os.fdopen(1, 'w', 0)
os.fstat(1)
del whatever
os.fstat(1)
Je se fait d'avoir un gestionnaire de contexte que je pense que fait exactement (ou presque atleast, dans mon cas, je Happen besoin d'un nom tempfile) ce que vous recherchez. Vous pouvez voir qu'il réutilise l'objet sys.stdout d'origine pour éviter la fermeture problématique.
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()))