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:

  1. Crea una copia di stdout (new)
  2. Crea un file temporaneo (tmp)
  3. reindirizzamento stdout in tmp
  4. python dicono di usare new come stdout
  5. Redirect tmp nel "reale" stdout
  6. python dicono di usare il "vero" stdout di nuovo
  7. 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

  1. 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?
  2. Sono ancora un po 'perplesso per l'utilizzo di dup e dup2. 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 il dup e dup2s nel mio script stanno facendo ^^
È stato utile?

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()))
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top