Bir dizeyi alt işlemeye nasıl geçiririm.Popen (stdin bağımsız değişkenini kullanarak)?


280

Aşağıdakileri yaparsam:

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

Alırım:

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'

Görünüşe göre bir cStringIO.StringIO nesnesi, alt işleme uygun bir dosya ördeğine yeterince yakın quack yapmıyor. Bu sorunu nasıl çözerim?


3
Bunun silinmesi ile cevabımı tartışmak yerine, onu bir yorum olarak ekliyorum ... Önerilen okuma: Doug Hellmann'ın Alt İşlemdeki Haftanın Python Modülü .
Daryl Spitzer

3
blog yazısı birden fazla hata içeriyor, örneğin ilk kod örneği:call(['ls', '-1'], shell=True) yanlış. Bunun yerine , alt sürecin etiket açıklamasında sık sorulan soruları okumanızı tavsiye ederim . Özellikle, neden subprocess.Pagen, argümanlar sıralandığında çalışmıyor? neden call(['ls', '-1'], shell=True)yanlış olduğunu açıklar . Blog yazısının altına yorum bıraktığımı hatırlıyorum, ancak şimdi bir nedenle görmüyorum.
jfs

Daha yeni için subprocess.runbkz. Stackoverflow.com/questions/48752152/…
Boris

Yanıtlar:


326

Popen.communicate() belgeleri:

İşlemin stdin'sine veri göndermek istiyorsanız, Stdin = PIPE ile Popen nesnesi oluşturmanız gerektiğini unutmayın. Benzer şekilde, sonuç demetinde Yok dışında bir şey elde etmek için de stdout = PIPE ve / veya stderr = PIPE vermeniz gerekir.

Değiştirme os.popen *

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

Uyarı Alt OS'yi dolduran ve engelleyen diğer işletim sistemi boru arabelleklerinden herhangi birinden kaynaklanan kilitlenmeleri önlemek için stdin.write (), stdout.read () veya stderr.read () yerine iletişim () kullanın.

Böylece örneğiniz aşağıdaki gibi yazılabilir:

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
# ->

Geçerli Python 3 sürümünde, subprocess.rungirdiyi harici bir komuta dize olarak geçirmek ve çıkış durumunu ve çıktısını bir çağrıda dize olarak almak için kullanabilirsiniz:

#!/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
# -> 

3
Bu uyarıyı kaçırdım. Sorduğuma sevindim (cevabı bulduğumu düşünmüş olmama rağmen).
Daryl Spitzer

11
Bu iyi bir çözüm DEĞİLDİR. Özellikle, tüm stdout'un gelmesini beklemeniz gerektiğinden, bunu yaparsanız p.stdout.readline çıktısını eşzamansız olarak işleyemezsiniz. Aynı zamanda bellek verimsizdir.
OTZ

7
@OTZ Daha iyi bir çözüm nedir?
Nick T

11
@Nick T: " daha iyi " bağlama bağlıdır. Newton yasaları, uygulanabilir oldukları etki alanı için iyidir, ancak GPS tasarlamak için özel göreliliğe ihtiyacınız vardır. Bkz . Bir alt işlemde engellemeyen okuma. Python'da PIPE .
jfs

9
Ancak iletişim için NOT'a dikkat edin : "veri boyutu büyük veya sınırsız ise bu yöntemi kullanmayın"
Owen

44

Bu geçici çözümü buldum:

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

Daha iyisi var mı?


25
@Moe: stdin.write()kullanımı önerilmez, p.communicate()kullanılmalıdır. Cevabımı gör.
jfs

11
Alt işlem belgelerine göre: Uyarı - Diğer işletim sistemi boru arabelleklerinin doldurulması ve engellenmesi nedeniyle oluşabilecek kilitlenmeleri önlemek için .stdin.write, .stdout.read veya .stderr.read yerine iletişim () kullanın.
Jason Mock

1
Stdout / err'nizin hiç dolmayacağından eminseniz (örneğin, bir dosyaya gidiyor veya başka bir iş parçacığı yiyorsa) ve sınırsız miktarda veriye sahipseniz, bunu yapmanın iyi bir yol olduğunu düşünüyorum. stdin'e gönderilecek.
Lucretiel

1
Özellikle, bu şekilde yapılması stdin'in kapalı olmasını sağlar, böylece alt işlemler sonsuza kadar girdi tüketen biriyse communicate, boruyu kapatır ve işlemin zarif bir şekilde bitmesine izin verir.
Lucretiel

@Lucretiel, süreç sonsuza kadar stdin tüketirse, muhtemelen sonsuza kadar stdout yazabilir, bu yüzden çok yönlü tamamen farklı tekniklere ihtiyacımız olurdu ( argüman olmadan bile read(), ondan olamaz communicate()).
Charles Duffy

25

Kimse bir boru oluşturma önerdi biraz şaşırdım, bence bir alt işlem stdin için bir dize geçmek için en basit yoludur:

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

subprocess.check_call(['your-command'], stdin=read)

2
osVe subprocessdokümantasyon hem Birincisini üzerinde ikinci tercih gerektiğini kabul ediyoruz. Bu, (biraz daha az özlü) bir standart değiştirmeye sahip eski bir çözümdür; kabul edilen cevap ilgili dokümanları alıntılar.
üçlü

1
Bunun doğru olduğundan emin değilim, üçlü. Alıntılanan belgeler, işlem tarafından oluşturulan boruları kullanmanın neden zor olduğunu söylüyor, ancak bu çözümde bir boru oluşturuyor ve içeri aktarıyor. İşlem başladıktan sonra boruların yönetilmesinde olası kilitlenme problemlerini önlediğine inanıyorum.
Graham Christensen

os.popen, alt süreç lehine kullanımdan kaldırıldı
hd1

2
-1: kilitlenmeye yol açar, veri kaybedebilir. Bu işlevsellik, alt işlem modülü tarafından zaten sağlanmıştır. Kötü yeniden uygulamak yerine kullanın (bir OS boru tamponundan daha büyük bir değer yazmaya çalışın)
jfs

En iyi adamı hakediyorsun, en basit ve en akıllı çözüm için teşekkür ederim
Felipe Buccioni

21

Python 3.4 veya daha iyisini kullanıyorsanız güzel bir çözüm var. Kullanım inputyerine argümanı stdinbir bayt argüman kabul argüman:

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

Bu, check_outputve için geçerlidir run, ancak değil callveya check_callbir nedenden dolayı.


5
@vidstige Haklısın, bu garip. Bunu bir Python hatası olarak kabul etmeyi düşünürüm, neden check_outputbir inputtartışmaya sahip olmamız için iyi bir neden görmüyorum , ama değil call.
Flimm

2
Bu Python 3.4+ için en iyi yanıttır (Python 3.6'da kullanmak). Gerçekten işe yaramaz check_callama işe yarıyor run. Ayrıca, belgelere göre bir kodlama bağımsız değişkeni ilettiğiniz sürece input = string ile de çalışır.
Nikolaos Georgiou

13

Ben python3 kullanıyorum ve stdin içine geçmeden önce dizenizi kodlamak gerektiğini öğrendim:

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)

5
Girdiyi özellikle kodlamanız gerekmez, sadece bayt benzeri bir nesne ister (örn. b'something'). Hata ve bayt olarak da geri dönecektir. Bunu önlemek istiyorsanız, size geçebilir universal_newlines=Trueiçin Popen. Sonra girişi str olarak kabul eder ve err / out komutunu str olarak döndürür.
Altı

2
Ancak dikkat edin, universal_newlines=Trueyeni satırlarınızı sisteminize uyacak şekilde de dönüştürür
Nacht - Reinstate Monica

1
Python 3 kullanıyorsanız, daha da uygun bir çözüm için cevabımı görün .
Flimm

12

Görünüşe göre bir cStringIO.StringIO nesnesi, alt işleme uyacak şekilde bir dosya ördeğine yeterince yakın quack yapmıyor.

Korkarım ki hayır. Boru, düşük düzeyli bir OS konseptidir, bu yüzden kesinlikle OS düzeyinde bir dosya tanımlayıcı tarafından temsil edilen bir dosya nesnesi gerektirir. Geçici çözümünüz doğru çözümdür.


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

3
fyi, tempfile.SpooledTemporaryFile .__ doc__ diyor ki: StringIO'dan belirli bir boyutu aştığında veya bir dosya gerektiğinde gerçek bir dosyaya geçiş yapmak için uzmanlaşmış geçici dosya sarmalayıcısı.
Doug F

5

Dikkat bu Popen.communicate(input=s)eğer sana sorun verebilir sçok büyük görünüşte üst süreç bunu tampon, çünkü daha önce çocuk alt işlemi bölmek o en azından "gizli olarak" açıklamaya göre, (bu noktada kullanılan belleğin "iki katı kadar" ihtiyacı anlam ve burada bulunan bağlantılı belgeler ). Benim özel durumumda, silk olarak tamamen genişletilen ve ancak daha sonra yazılan bir jeneratördü, stdinböylece ebeveyn süreci, çocuk yumurtlamadan hemen önce çok büyüktü ve çatallamak için bellek kalmadı:

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


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

4
Çünkü shell=Trueböylece yaygın sebepsiz kullanılır ve bu popüler bir sorudur edilir beni durumların çok olduğunu işaret izin Popen(['cmd', 'with', 'args'])kesinlikle daha iyi olduğunu Popen('cmd with args', shell=True)ve kabuk dizgeciklerine komuta ve argümanları kırmak zorunda ama başka türlü bir şey sağlamıyor yararlı, önemli miktarda karmaşıklık ekleyerek ve böylece saldırı yüzeyine.
Üçlü

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)

1

Python 3.7+ üzerinde şunları yapın:

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

ve muhtemelen capture_output=Truekomutu bir dize olarak çalıştırma çıktısını almak için eklemek isteyeceksiniz .

Python eski sürümlerinde, yerini text=Trueile universal_newlines=True:

subprocess.run(["grep", "f"], universal_newlines=True, input=my_data)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.