2015-03-08 4 views
5

Ich versuche, die Ausgabe eines Befehls zu einer Variablen ohne den Befehl zu übergeben, der denkt, dass es piped ist. Der Grund dafür ist, dass der fragliche Befehl unformatierten Text als Ausgabe gibt, wenn er ausgegeben wird, aber es gibt farbig formatierten Text, wenn er vom Terminal aus ausgeführt wird. Ich brauche diesen farbig formatierten Text.Python 3.4.3 subprocess.Popen Ausgabe des Befehls ohne Piping erhalten?

Bis jetzt habe ich ein paar Dinge ausprobiert. Ich habe versucht, Popen wie so:

output = subprocess.Popen(command, stdout=subprocess.PIPE) 
output = output.communicate()[0] 
output = output.decode() 
print(output) 

Dies läßt mich die Ausgabe drucken, aber es gibt mir die unformatierte Ausgabe, die ich bekomme, wenn der Befehl geleitet wird. Das macht Sinn, da ich es hier im Python-Code übergebe. Aber ich bin neugierig, ob es eine Möglichkeit gibt, die Ausgabe dieses Befehls direkt einer Variablen zuzuordnen, ohne dass der Befehl die verrohrte Version von sich selbst ausführt.

Ich habe auch versucht, die folgende Version, die statt auf check_output beruht:

output = subprocess.check_output(command) 
output = output.decode() 
print(output) 

Und wieder ich die gleiche unformatierte Ausgabe zu erhalten, die den Befehl, wenn der Befehl geleitet wird.

Gibt es eine Möglichkeit, die formatierte Ausgabe zu erhalten, die Ausgabe, die der Befehl normalerweise vom Terminal geben würde, wenn es nicht verrohrt wird?

+0

Ich denke, die "Färbung" ist eine Funktion der Shell/Tty. Die Farben sind nicht Teil der Befehlsausgabe. Sie können also nichts anderes als diese unformatierte Ausgabe abrufen. – GhostCat

+1

Hallo EddyG, manchmal stimmt das, aber Sie können selbst Farbcode an das Terminal senden. Das ist in diesem Fall der Fall. Ich überprüfte die C-Quelle des Befehls selbst und es sendet tatsächlich den Farbformatierungscode. Es ist also nicht nur die Tastatur. – nullified

+0

verwandt: [Letzte ungepufferte Zeile kann nicht gelesen werden] (http://stackoverflow.com/q/25923901/4279) – jfs

Antwort

9

pexpect Verwendung:

2.py:

import sys 

if sys.stdout.isatty(): 
    print('hello') 
else: 
    print('goodbye') 

subprocess:

import subprocess 

p = subprocess.Popen(
    ['python3.4', '2.py'], 
    stdout=subprocess.PIPE 
) 

print(p.stdout.read()) 

--output:-- 
goodbye 

pexpect:

import pexpect 

child = pexpect.spawn('python3.4 2.py') 

child.expect(pexpect.EOF) 
print(child.before) #Print all the output before the expectation. 

--output:-- 
hello 

Hier ist es mit grep --colour=auto :

import subprocess 

p = subprocess.Popen(
    ['grep', '--colour=auto', 'hello', 'data.txt'], 
    stdout=subprocess.PIPE 
) 

print(p.stdout.read()) 

import pexpect 

child = pexpect.spawn('grep --colour=auto hello data.txt') 
child.expect(pexpect.EOF) 
print(child.before) 

--output:-- 
b'hello world\n' 
b'\x1b[01;31mhello\x1b[00m world\r\n' 
+1

upvote. Soweit ich das beurteilen kann, ist es die einzige Antwort mit einem Code-Beispiel, das tatsächlich funktioniert. – jfs

+1

können Sie ['pexpect.runu()'] (http://pexpect.readthedocs.org/en/latest/api/pexpect.html?highlight=runu#pexpect.runu) verwenden, wenn Sie nur die Ausgabe benötigen als eine Schnur. – jfs

+0

@ J.F. Sebastian, Nizza. Ich lese gerade die Dokumente. Hätte etwas Ekel mit dieser EOF-Konstante gespart. – 7stud

1

Viele Programme schalten Farbdruckcodes automatisch aus, wenn sie erkennen, dass sie nicht direkt an ein Terminal angeschlossen sind. Viele Programme haben ein Flag, so dass Sie die Farbausgabe erzwingen können. Sie könnten dieses Flag zu Ihrem Prozessaufruf hinzufügen. Zum Beispiel:

grep "search term" inputfile.txt 
# prints colour to the terminal in most OSes 

grep "search term" inputfile.txt | less 
# output goes to less rather than terminal, so colour is turned off 

grep "search term" inputfile.txt --color | less 
# forces colour output even when not connected to terminal 

Seien Sie gewarnt, obwohl. Die eigentliche Farbausgabe erfolgt über das Terminal. Das Terminal interpretiert Sonderzeichen-Espace-Codes und ändert die Textfarbe und die Hintergrundfarbe entsprechend. Ohne das Terminal, um die Farbcodes zu interpretieren, sehen Sie nur den Text in schwarz mit diesen durchgestrichenen Escape-Codes.

+0

Danke Dunes, das ist auf dem richtigen Weg, aber es ist nicht genug. Die App für diesen Befehl hat in diesem Fall eine Konfigurationsdatei. Benutzer können die Farbe auf nie, automatisch oder immer einstellen. Also kann ich die Farbe nicht selbst erzwingen, ich muss versuchen, genau die Ausgabe zu erhalten, die der nicht verlegte Befehl geben würde. Wenn ich die Farbe erzwinge, dann überschreibe ich die Benutzerkonfiguration, wenn sie nie geschehen ist. – nullified

+0

@nullified: Wenn Sie den Befehl 'script' emulieren möchten, können Sie [' pty.spawn() 'oder' pexpect'] (http://stackoverflow.com/a/25945031/4279) – jfs

4

Ja, Sie können die pty module verwenden.

>>> import subprocess 
>>> p = subprocess.Popen(["ls", "--color=auto"], stdout=subprocess.PIPE) 
>>> p.communicate()[0] 
# Output does not appear in colour 

Mit pty:

import subprocess 
import pty 
import os 

master, slave = pty.openpty() 
p = subprocess.Popen(["ls", "--color=auto"], stdout=slave) 
p.communicate() 
print(os.read(master, 100)) # Print 100 bytes 
# Prints with colour formatting info 

Hinweis aus der Dokumentation:

Da Pseudo-Terminal Handling sehr plattformabhängig ist, gibt -Code ist es nur für Linux zu tun. (Der Linux-Code sollte auf anderen Plattformen arbeiten, aber noch nicht getestet.)

A weniger als schöne Art und Weise, die gesamte Ausgabe in einem Rutsch zu Ende zu lesen:

def num_bytes_readable(fd): 
    import array 
    import fcntl 
    import termios 
    buf = array.array('i', [0]) 
    if fcntl.ioctl(fd, termios.FIONREAD, buf, 1) == -1: 
     raise Exception("We really should have had data") 
    return buf[0] 

print(os.read(master, num_bytes_readable(master))) 

Edit: schönere Art und Weise, den Inhalt sofort dank @Antti Haapala bekommen:

os.close(slave) 
f = os.fdopen(master) 
print(f.read()) 

Edit: Menschen sind zu Recht darauf hin, dass dies eine Sackgasse, wenn der Prozess eine große Leistung erzeugt, so @Antti Haapala des Antwort ist besser.

+0

verwenden os.fdopen', um den Masterdeskriptor als Python-Datei zu öffnen –

+0

Ja, 'pty' kann den Befehl dazu verleiten, zu denken, dass er interaktiv in einem Terminal ausgeführt wird (obwohl das Programm' --color'-Option wie 'ls' bereitstellt) (Sie könnten einfach die Ausgabe pipen) 1. Sie sollten erwähnen, dass "pty" für Linux ist (es kann auch an anderen Computern funktionieren). Sie müssen 'p.communicate()' nicht aufrufen, wenn Sie 'stdout = slave' verwenden -' p.wait() 'ist genug. Sie sollten wahrscheinlich von 'Master' lesen, während der Befehl noch läuft, andernfalls kann das Programm für immer hängen bleiben, nachdem es die entsprechenden Ausgabepuffer gefüllt hat, [Codebeispiel] (http://stackoverflow.com/a/20509641/4279) <- 'pexpect' – jfs

+0

Das Schließen des' Slave'-Dateideskriptors vor dem Lesen der Ausgabe von 'Master' kann zu einem E/A-Fehler führen (sollte es aber nicht). Ich musste [os.close (slave_fd) 'nach der' while 1' setzen, die die Ausgabe liest, damit der Code funktioniert] (http://stackoverflow.com/questions/12419198/python-subprocess-readlines-hangs/ 12471855 # 12471855). Außerdem kann 'f.read()' für immer hängen bleiben - probiere es aus (ich bin mir nicht ganz sicher, dass es schon lange her ist). – jfs

3

Ein Arbeits polyglot Beispiel (funktioniert auf das gleiche für Python 2 und Python 3) mit pty.

import subprocess 
import pty 
import os 
import sys 

master, slave = pty.openpty() 
# direct stderr also to the pty! 
process = subprocess.Popen(
    ['ls', '-al', '--color=auto'], 
    stdout=slave, 
    stderr=subprocess.STDOUT 
) 

# close the slave descriptor! otherwise we will 
# hang forever waiting for input 
os.close(slave) 

def reader(fd): 
    try: 
     while True: 
      buffer = os.read(fd, 1024) 
      if not buffer: 
       return 

      yield buffer 

    # Unfortunately with a pty, an 
    # IOError will be thrown at EOF 
    # On Python 2, OSError will be thrown instead. 
    except (IOError, OSError) as e: 
     pass 

# read chunks (yields bytes) 
for i in reader(master): 
    # and write them to stdout file descriptor 
    os.write(1, b'<chunk>' + i + b'</chunk>') 
+0

1. Es sollte nicht auf Python 3 arbeiten, versuchen Sie stattdessen 'os.write (1, b'a ')'. 2. Unter Linux, wenn Sie 'slave' schließen, bevor die Daten gelesen werden, kann eine Ausnahme ausgelöst werden. 3. Verwenden Sie 'EnvironmentError', um sowohl 'IOError',' OSError' zu fangen (sie sind in Python 3 alle gleich). – jfs

+0

behoben 1; Wie für 2, ist die Ausnahmebehandlung dort, um die Ausnahme zu fangen; und 3 stelle ich sie explizit in das Tupel, so dass man versteht, dass der Fehler nicht speziell IOError ** oder ** OSError ist. –

+0

bestätigen Sie, dass die Ausnahme * immer * ausgelöst wird und alle Daten immer gelesen werden (vorausgesetzt, dass keine anderen Fehler vorliegen)? Ich verstehe den Punkt beim Schreiben '(IOError, OSError)' nicht anstelle von 'EnvironmentError'. – jfs