2015-03-08 47 views
5

Próbuję przypisać wyjście polecenia do zmiennej bez polecenia myślenie, że jest podłączony. Powodem tego jest to, że dane polecenie podaje niesformatowany tekst jako wynik, jeśli jest podłączony, ale daje tekst sformatowany w kolorze, jeśli jest uruchamiany z terminala. Muszę uzyskać ten sformatowany tekst w kolorze.Python 3.4.3 subprocess.Popen uzyskać wyjście polecenia bez rurociągów?

Do tej pory próbowałem kilku rzeczy. Próbowałem POPEN tak:

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

To pozwoli mi wydrukować wyjście, ale to daje mi wyjście niesformatowany że dostanę gdy polecenie jest rurami. To ma sens, ponieważ robię to tutaj w kodzie Pythona. Ale jestem ciekawy, czy istnieje sposób, aby przypisać wyjście tego polecenia bezpośrednio do zmiennej, bez polecenia uruchamiającego swoją wersję potokową.

Próbowałem również następującą wersję, która opiera się na check_output Zamiast:

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

I znowu uzyskać taki sam efekt niesformatowany że powraca dowodzenia, gdy polecenie jest przetłaczany.

Czy istnieje sposób na uzyskanie sformatowanego wyjścia, na wyjściu, które polecenie zwykle wydaje z terminala, gdy nie jest podłączone?

+0

Myślę, że "kolorystyka" jest funkcją powłoki/tty. Kolory nie są częścią wyjścia polecenia. Więc nie będziesz w stanie odzyskać niczego poza tym niesformatowanym wynikiem. – GhostCat

+1

Cześć EddyG, czasami to prawda, ale sam możesz przesłać kod formatowania kolorów do terminalu. Tak jest w tym przypadku. Sprawdziłem dwukrotnie źródło C samej komendy i rzeczywiście wysłałem kod formatowania kolorów. Więc to nie jest tylko tty. – nullified

+0

powiązane: [Ostatnia linia niebuforowana nie może być odczytana] (http://stackoverflow.com/q/25923901/4279) – jfs

Odpowiedz

9

Korzystanie pexpect:

2.py:

import sys 

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

podproces:

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 

Oto ona z 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. O ile mogę powiedzieć, jest to jedyna odpowiedź z przykładowym kodem, który faktycznie działa. – jfs

+1

można użyć ['pexpect.runu()'] (http://pexpect.readthedocs.org/en/latest/api/pexpect.html?highlight=runu#pexpect.runu), jeśli wszystko, czego potrzebujesz, to uzyskać wyjście jako ciąg. – jfs

+0

@ J.F. Sebastian, Nice. Po prostu czytam dokumenty. Oszczędziło trochę grzebania z tą stałą EOF. – 7stud

1

Wiele programów automatycznie wyłącza kody drukowania w kolorze, gdy wykryją, że nie są podłączone bezpośrednio do terminala. Wiele programów będzie miało flagę, dzięki czemu możesz wymusić wydruk kolorowy. Możesz dodać tę flagę do swojego wywołania procesu. Na przykład:

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 

Ostrzegam jednak. Rzeczywisty wydruk kolorów jest wykonywany przez terminal. Terminal interpretuje kody espace specjalnego znaku i odpowiednio zmienia kolor tekstu i kolor tła. Bez terminala do interpretowania kodów kolorów zobaczysz tylko tekst w kolorze czarnym z tymi kodami ucieczkowymi przeplatanymi w całym tekście.

+0

Dzięki Dunes, to jest na dobrej drodze, ale to nie wystarczy. Aplikacja dla tego polecenia ma w tym przypadku plik konfiguracyjny. Użytkownicy mogą ustawić kolor na nigdy, auto lub zawsze. Więc nie mogę wymusić na sobie koloru, muszę spróbować uzyskać dokładny wynik, jaki dałoby polecenie nieprzelotowe. Jeśli wymuszam kolor, to nadpisam konfigurację użytkowników, jeśli nigdy tego nie zrobili. – nullified

+0

@nullified: jeśli chcesz emulować komendę 'script', możesz spróbować [' pty.spawn() 'lub' pexpect'] (http://stackoverflow.com/a/25945031/4279) – jfs

4

Tak, można użyć pty module.

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

Z 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 

Uwaga od docs:

Ponieważ obsługa pseudo-terminal jest wysoce zależny od platformy, tam jest kod to zrobić tylko dla systemu Linux. (Kod Linux ma pracować na innych platformach, ale nie został jeszcze przetestowany.)

Mniej niż piękny sposób czytania całe wyjście do końca za jednym zamachem:

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: ładniejszy sposób na uzyskanie zawartości naraz dzięki @Antti Haapala:

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

Edit: ludzie mają prawo zwrócić uwagę, że będzie to impas, jeśli proces generuje dużą moc, więc @Antti Haapala użytkownika odpowiedź jest lepsza.

+0

Możesz użyć '' os.fdopen', aby otworzyć deskryptor główny jako plik Pythona –

+0

tak, 'pty' może zamaskować polecenie, myśląc, że jest uruchamiane interaktywnie w terminalu (chociaż jeśli program udostępnia opcję' --color', taką jak 'ls' ; możesz po prostu potokować wyjście) 1. Powinieneś wspomnieć, że 'pty' jest dla Linuksa (może również działać na innych * nicesach). Nie musisz wywoływać 'p.communicate()' jeśli używasz 'stdout = slave' - wystarczy' p.wait() '. Powinieneś prawdopodobnie czytać z 'master', podczas gdy polecenie nadal działa, w przeciwnym razie program może się zawiesić na zawsze po wypełnieniu odpowiednich buforów wyjściowych, [przykład kodu] (http://stackoverflow.com/a/20509641/4279) <- 'pexpect' – jfs

+0

Zamknięcie deskryptora pliku' slave' przed odczytaniem danych wyjściowych z 'master' może spowodować błąd I/O (nie powinien, ale robi to). Musiałem [wstawić 'os.close (slave_fd)' po 'while 1', który czyta dane wyjściowe, aby kod działał] (http://stackoverflow.com/questions/12419198/python-subprocess-readlines-hangs/ 12471855 # 12471855). Również 'f.read()' może wisieć na zawsze - spróbuj (nie jestem całkowicie pewien, że to było dawno temu). – jfs

3

Działający przykład polyglot (identycznie dla Pythonie 2 Python 3), przy 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. to nie powinno działać na Pythonie 3, zamiast tego spróbuj 'os.write (1, b'a ')'. 2. W systemie Linux po zamknięciu 'slave' przed odczytaniem danych może zostać zgłoszony wyjątek. 3. Użyj 'EnvironmentError', aby przechwycić zarówno' IOError', 'OSError' (wszystkie są takie same w Pythonie 3). – jfs

+0

poprawił 1; jak dla 2, obsługa wyjątków jest tam, aby wychwycić wyjątek; i 3, umieszczam je jawnie w krotce, aby zrozumieć, że błąd nie jest konkretnie IOError ** lub ** OSError. –

+0

czy twierdzisz, że wyjątek jest zawsze zawsze podniesiony i wszystkie dane są zawsze czytane (zakładając, że nie ma innych błędów)? Nie rozumiem sensu pisania '(IOError, OSError)' zamiast 'EnvironmentError'. – jfs