Frage

Dies ist eine Follow -up -Frage von hier.


Wohin ich gehen will

Ich möchte in der Lage sein, den STDOut vorübergehend in eine TEMP -Datei umzuleiten, während Python immer noch in STDOut drucken kann. Dies würde die folgenden Schritte beinhalten:

  1. Erstellen Sie eine Kopie von stdout (new)
  2. Erstellen Sie eine TEMP -Datei (tmp)
  3. Stdout in tmp
  4. Sagen Sie Python, sie sollen verwenden new als Stdout
  5. Umleiten tmp in den "echten" Stdout
  6. Sagen Sie Python, dass er den "echten" Stdout erneut verwenden soll
  7. Lesen und schließen tmp

Implementierung

Ich habe versucht, die oben genannten folgenden Weise zu implementieren:

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

Ich möchte hier eine kleine Pause einlegen, um zusammenzufassen.
Die Ausgabe zur Konsole bis hierher sollte lautet:

0.1
0.2
print

während sil sollte so aussehen: ['0.3\n']. Also funktioniert alles bis hier wie ein Zauber. Wenn ich das Skript oben jedoch wieder so wiederhole:

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

Ein Fehler tritt auf und die Ausgabe sieht so aus:

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

während sil liest: ['0.3\n', ''].

Mit anderen Worten: die zweite Func("1.3", True) ist nicht in der Lage, in die TEMP -Datei zu schreiben.

Fragen

  1. Zunächst möchte ich wissen, warum mein Skript nicht so funktioniert, wie ich es möchte. Das heißt, warum ist es in der ersten Hälfte des Skripts nur möglich, in die TEMP -Datei zu schreiben?
  2. Ich bin immer noch ein wenig verwirrt über die Verwendung von dup und dup2. Während ich denke, ich verstehe, wie die Umleitung von stdout in eine temporäre Datei funktioniert, weiß ich jetzt total, warum os.dup2(new, 1) Tun, was es tut. Vielleicht könnte die Antwort auf das ausgehen, was alle dup und dup2s in meinem Skript machen ^^ ^^
War es hilfreich?

Lösung

Der Grund, warum Sie einen "schlechten Dateideskriptor" erhalten, ist, dass der Müllsammler die STDOut -FD für Sie schließt. Betrachten Sie diese beiden Zeilen:

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

Wenn nun die zweite dieser beiden ausgeführt wird, fällt die Referenzzahl des ersten Dateiobjekts auf Null und der Müllsammler zerstört es. Dateiobjekte schließen ihre zugehörige FD, wenn sie zerstört werden, und diese FD ist zufällig 1 = stdout. Sie müssen also sehr vorsichtig sein, wie Sie Objekte zerstören os.fdopen.

Hier ist ein kleines Beispiel, um das Problem zu zeigen. os.fstat wird nur als Beispielfunktion verwendet, die den Fehler "schlechter Dateideskriptor" auslöst, wenn Sie ihm eine geschlossene FD übergeben.

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

Ich habe tatsächlich einen Kontextmanager, von dem ich denke, dass es genau (oder mindestens mindestens mindestens ich in meinem Fall eine benannte Tempfile benötigt), wonach Sie suchen. Sie können sehen, dass es das ursprüngliche Sys.stdout -Objekt wiederverwendet, um das enge problematische Problem zu vermeiden.

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()))
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top