Jak przekazać ciąg do podprocesu.Popen (używając argumentu stdin)?

280

Jeśli wykonam następujące czynności:

import subprocess
from cStringIO import StringIO
subprocess.Popen(['grep','f'],stdout=subprocess.PIPE,stdin=StringIO('one\ntwo\nthree\nfour\nfive\nsix\n')).communicate()[0]

Dostaję:

Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "/build/toolchain/mac32/python-2.4.3/lib/python2.4/subprocess.py", line 533, in __init__
    (p2cread, p2cwrite,
  File "/build/toolchain/mac32/python-2.4.3/lib/python2.4/subprocess.py", line 830, in _get_handles
    p2cread = stdin.fileno()
AttributeError: 'cStringIO.StringI' object has no attribute 'fileno'

Najwyraźniej obiekt cStringIO.StringIO nie kwakuje wystarczająco blisko kaczki pliku, aby pasował do podprocesu.Popen. Jak obejść ten problem?

Daryl Spitzer
źródło
3
Zamiast kwestionować moją odpowiedź dotyczącą tego usunięcia, dodaję ją jako komentarz ... Zalecane lektury: post na blogu Douga Hellmanna na temat modułu tygodnia na podprocesie .
Daryl Spitzer
3
post na blogu zawiera wiele błędów, np. pierwszy przykład kodu:call(['ls', '-1'], shell=True) jest niepoprawny. Zamiast tego polecam przeczytanie typowych pytań z opisu znacznika podprocesu . W szczególności: Dlaczego podproces.Popen nie działa, gdy argument jest sekwencją? wyjaśnia, dlaczego call(['ls', '-1'], shell=True)jest źle. Pamiętam, jak zostawiam komentarze pod postem na blogu, ale z jakiegoś powodu nie widzę ich teraz.
jfs
Nowsze subprocess.runpatrz stackoverflow.com/questions/48752152/…
Boris

Odpowiedzi:

326

Popen.communicate() dokumentacja:

Zauważ, że jeśli chcesz wysłać dane do wejścia standardowego procesu, musisz utworzyć obiekt Popen o parametrze stdin = PIPE. Podobnie, aby uzyskać wynik inny niż None w krotce wynikowej, musisz również podać stdout = PIPE i / lub stderr = PIPE.

Zastępowanie os.popen *

    pipe = os.popen(cmd, 'w', bufsize)
    # ==>
    pipe = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE).stdin

Ostrzeżenie: Użyj komunikacji () zamiast stdin.write (), stdout.read () lub stderr.read (), aby uniknąć zakleszczeń spowodowanych zapełnianiem się i blokowaniem procesu potomnego przez inne bufory potoków systemu operacyjnego.

Twój przykład można zapisać następująco:

from subprocess import Popen, PIPE, STDOUT

p = Popen(['grep', 'f'], stdout=PIPE, stdin=PIPE, stderr=STDOUT)    
grep_stdout = p.communicate(input=b'one\ntwo\nthree\nfour\nfive\nsix\n')[0]
print(grep_stdout.decode())
# -> four
# -> five
# ->

W bieżącej wersji Python 3 można użyć subprocess.run, aby przekazać dane wejściowe jako ciąg znaków do polecenia zewnętrznego i uzyskać jego status wyjścia, a dane wyjściowe jako ciąg znaków z powrotem w jednym wywołaniu:

#!/usr/bin/env python3
from subprocess import run, PIPE

p = run(['grep', 'f'], stdout=PIPE,
        input='one\ntwo\nthree\nfour\nfive\nsix\n', encoding='ascii')
print(p.returncode)
# -> 0
print(p.stdout)
# -> four
# -> five
# -> 
jfs
źródło
3
Przegapiłem to ostrzeżenie. Cieszę się, że zapytałem (choć myślałem, że mam odpowiedź).
Daryl Spitzer
11
To NIE jest dobre rozwiązanie. W szczególności nie możesz asynchronicznie przetwarzać danych wyjściowych p.stdout.readline, jeśli to zrobisz, ponieważ będziesz musiał poczekać na cały stdout. Jest to również nieefektywne pod względem pamięci.
OTZ,
7
@OTZ Jakie jest lepsze rozwiązanie?
Nick T
11
@Nick T: „ lepszy ” zależy od kontekstu. Prawa Newtona są dobre dla dziedziny, w której mają zastosowanie, ale do zaprojektowania GPS potrzebujesz szczególnej teorii względności. Zobacz Nieblokujący odczyt w podprocesie.PIPE w pythonie .
jfs
9
Zwróć jednak uwagę na UWAGĘ dotyczącą komunikacji : „nie używaj tej metody, jeśli rozmiar danych jest duży lub nieograniczony”
Owen
44

Zrozumiałem to obejście:

>>> p = subprocess.Popen(['grep','f'],stdout=subprocess.PIPE,stdin=subprocess.PIPE)
>>> p.stdin.write(b'one\ntwo\nthree\nfour\nfive\nsix\n') #expects a bytes type object
>>> p.communicate()[0]
'four\nfive\n'
>>> p.stdin.close()

Czy jest lepszy?

Daryl Spitzer
źródło
25
@Moe: stdin.write()korzystanie nie jest p.communicate()zalecane , należy je stosować. Zobacz moją odpowiedź.
jfs
11
Zgodnie z dokumentacją podprocesu: Ostrzeżenie - należy użyć metody communic () zamiast .stdin.write, .stdout.read lub .stderr.read, aby uniknąć zakleszczeń spowodowanych zapełnianiem się i blokowaniem procesu potomnego przez inne bufory potoków systemu operacyjnego.
Jason Mock,
1
Myślę, że to dobry sposób na zrobienie tego, jeśli masz pewność, że twoje standardowe wyjście / błąd nigdy się nie zapełni (na przykład, idzie do pliku lub inny wątek je zjada) i masz nieograniczoną ilość danych zostać wysłanym na standardowe wejście.
Lucretiel,
1
W szczególności, robienie tego w ten sposób nadal zapewnia, że ​​stdin jest zamknięte, tak że jeśli podprocesy zużywają dane wejściowe na zawsze, communicatezamkną potok i pozwolą procesowi zakończyć się z wdziękiem.
Lucretiel,
@ Lucretiel, jeśli proces zużywa stdin na zawsze, to prawdopodobnie nadal może zapisywać stdout na zawsze, więc potrzebowalibyśmy całkowicie różnych technik (nie da się tego read()zrobić, tak jak communicate()to bez argumentów).
Charles Duffy
25

Jestem trochę zaskoczony, że nikt nie zasugerował stworzenia fajki, co jest moim zdaniem najprostszym sposobem na przekazanie łańcucha do standardowego podprocesu:

read, write = os.pipe()
os.write(write, "stdin input here")
os.close(write)

subprocess.check_call(['your-command'], stdin=read)
Graham Christensen
źródło
2
Zarówno dokumentacja, jak osi subprocessdokumentacja zgadzają się, że powinieneś preferować tę drugą opcję. Jest to starsze rozwiązanie, które ma (nieco mniej zwięzłe) standardowe zamienniki; przyjęta odpowiedź przytacza stosowną dokumentację.
tripleee
1
Nie jestem pewien, czy to prawda, potrójny. Cytowana dokumentacja wyjaśnia, dlaczego trudno jest używać rur utworzonych w procesie, ale w tym rozwiązaniu tworzy rurę i przepuszcza ją. Uważam, że pozwala uniknąć potencjalnych problemów związanych z zakleszczeniem zarządzania rurami po rozpoczęciu procesu.
Graham Christensen
os.popen jest przestarzałe na rzecz podprocesu
hd1
2
-1: prowadzi do impasu, może utracić dane. Ta funkcja jest już zapewniona przez moduł podprocesu. Użyj go zamiast źle go wdrożyć (spróbuj zapisać wartość, która jest większa niż bufor potoku systemu operacyjnego)
jfs
Zasługujesz na najlepszego dobrego człowieka, dziękuję za najprostsze i najmądrzejsze rozwiązanie
Felipe Buccioni
21

Jest piękne rozwiązanie, jeśli używasz języka Python 3.4 lub nowszego. Użyj inputargumentu zamiast stdinargumentu, który akceptuje argument bajtów:

output = subprocess.check_output(
    ["sed", "s/foo/bar/"],
    input=b"foo",
)

Działa to dla check_outputa run, ale nie calllub check_callz jakiegoś powodu.

Flimm
źródło
5
@vidstige Masz rację, to dziwne. Rozważałbym zgłoszenie tego jako błędu Pythona, nie widzę żadnego dobrego powodu, dla którego check_outputpowinienem mieć inputargument, ale nie call.
Flimm,
2
To najlepsza odpowiedź dla Pythona 3.4+ (używając go w Pythonie 3.6). Rzeczywiście nie działa, check_callale działa run. Działa również z ciągiem input =, o ile przekazujesz argument kodowania zgodnie z dokumentacją.
Nikolaos Georgiou
13

Korzystam z Python3 i odkryłem, że musisz zakodować swój ciąg, zanim będziesz mógł przekazać go do standardowego wejścia:

p = Popen(['grep', 'f'], stdout=PIPE, stdin=PIPE, stderr=PIPE)
out, err = p.communicate(input='one\ntwo\nthree\nfour\nfive\nsix\n'.encode())
print(out)
co było do okazania
źródło
5
Nie musisz specjalnie kodować danych wejściowych, potrzebuje tylko obiektu podobnego do bajtów (np b'something'.). Zwróci również błędy i zniknie jako bajty. Jeśli chcesz tego uniknąć, możesz przejść universal_newlines=Truedo Popen. Następnie zaakceptuje dane wejściowe jako str i zwróci również err / out jako str.
Sześć
2
Ale uwaga, universal_newlines=Trueprzekonwertuje również twoje nowe wiersze, aby pasowały do ​​twojego systemu
Nacht - Przywróć Monikę
1
Jeśli używasz Python 3, zobacz moją odpowiedź na jeszcze wygodniejsze rozwiązanie.
Flimm,
12

Najwyraźniej obiekt cStringIO.StringIO nie kwakuje wystarczająco blisko kaczki pliku, aby dopasować się do podprocesu.

Obawiam się że nie. Potok jest koncepcją niskiego poziomu systemu operacyjnego, więc absolutnie wymaga obiektu pliku reprezentowanego przez deskryptor pliku na poziomie systemu operacyjnego. Twoje obejście jest właściwe.

Dan Lenski
źródło
7
from subprocess import Popen, PIPE
from tempfile import SpooledTemporaryFile as tempfile
f = tempfile()
f.write('one\ntwo\nthree\nfour\nfive\nsix\n')
f.seek(0)
print Popen(['/bin/grep','f'],stdout=PIPE,stdin=f).stdout.read()
f.close()
Michael Waddell
źródło
3
fyi, tempfile.SpooledTemporaryFile .__ doc__ mówi: Tymczasowe opakowanie pliku, specjalizujące się w przełączaniu z StringIO na plik rzeczywisty, gdy przekracza on określony rozmiar lub gdy potrzebny jest plik.
Doug F
5

Uważaj, ponieważ Popen.communicate(input=s)może być kłopotliwy, jeśli sjest zbyt duży, ponieważ najwyraźniej proces nadrzędny buforuje go przed rozwidleniem podprocesu potomnego, co oznacza, że ​​potrzebuje „dwukrotnie więcej” zużytej pamięci w tym momencie (przynajmniej zgodnie z wyjaśnieniem „pod maską” i powiązaną dokumentację tutaj ). W moim szczególnym przypadku sbył to generator, który najpierw został w pełni rozwinięty, a dopiero potem napisany w nim, stdinaby proces nadrzędny był ogromny tuż przed spawnowaniem dziecka i nie pozostawiono pamięci, aby go rozwidlić:

File "/opt/local/stow/python-2.7.2/lib/python2.7/subprocess.py", line 1130, in _execute_child self.pid = os.fork() OSError: [Errno 12] Cannot allocate memory

Lord Henry Wotton
źródło
5
"""
Ex: Dialog (2-way) with a Popen()
"""

p = subprocess.Popen('Your Command Here',
                 stdout=subprocess.PIPE,
                 stderr=subprocess.STDOUT,
                 stdin=PIPE,
                 shell=True,
                 bufsize=0)
p.stdin.write('START\n')
out = p.stdout.readline()
while out:
  line = out
  line = line.rstrip("\n")

  if "WHATEVER1" in line:
      pr = 1
      p.stdin.write('DO 1\n')
      out = p.stdout.readline()
      continue

  if "WHATEVER2" in line:
      pr = 2
      p.stdin.write('DO 2\n')
      out = p.stdout.readline()
      continue
"""
..........
"""

out = p.stdout.readline()

p.wait()
Lucien Hercaud
źródło
4
Ponieważ shell=Truejest to tak często używane bez uzasadnionego powodu i jest to popularne pytanie, pozwolę sobie zauważyć, że istnieje wiele sytuacji, w których Popen(['cmd', 'with', 'args'])zdecydowanie lepiej jest Popen('cmd with args', shell=True)mieć powłokę dzielącą polecenia i argumenty na tokeny, ale w żaden inny sposób nie dostarczając niczego przydatne, jednocześnie dodając znaczną złożoność, a tym samym atakując powierzchnię.
tripleee
2
p = Popen(['grep', 'f'], stdout=PIPE, stdin=PIPE, stderr=STDOUT)    
p.stdin.write('one\n')
time.sleep(0.5)
p.stdin.write('two\n')
time.sleep(0.5)
p.stdin.write('three\n')
time.sleep(0.5)
testresult = p.communicate()[0]
time.sleep(0.5)
print(testresult)
dusan
źródło
1

W Pythonie 3.7+ wykonaj następujące czynności:

my_data = "whatever you want\nshould match this f"
subprocess.run(["grep", "f"], text=True, input=my_data)

i prawdopodobnie będziesz chciał dodać, capture_output=Trueaby uzyskać wynik uruchomienia polecenia jako ciąg.

W starszych wersjach Pythona, wymienić text=Truez universal_newlines=True:

subprocess.run(["grep", "f"], universal_newlines=True, input=my_data)
Boris
źródło