Bagaimana cara mengirim string ke subprocess.Popen (menggunakan argumen stdin)?

280

Jika saya melakukan hal berikut:

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

Saya mendapat:

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'

Rupanya objek cStringIO.StringIO tidak berdetak cukup dekat ke file bebek yang sesuai dengan subprocess.Popen. Bagaimana saya mengatasi ini?

Daryl Spitzer
sumber
3
Alih-alih memperdebatkan jawaban saya dengan ini yang dihapus, saya menambahkannya sebagai komentar ... Bacaan yang disarankan: Modul posting Minggu Ini dari Doug Hellmann tentang posting blog tentang subproses .
Daryl Spitzer
3
posting blog berisi banyak kesalahan, misalnya, contoh kode pertama:call(['ls', '-1'], shell=True) salah. Saya sarankan untuk membaca pertanyaan umum dari deskripsi tag subproses sebagai gantinya. Secara khusus, Mengapa subprocess.Popen tidak berfungsi ketika args adalah urutan? menjelaskan mengapa call(['ls', '-1'], shell=True)itu salah. Saya ingat meninggalkan komentar di bawah posting blog tetapi saya tidak melihatnya sekarang karena beberapa alasan.
jfs
Untuk yang lebih baru subprocess.runlihat stackoverflow.com/questions/48752152/…
Boris

Jawaban:

326

Popen.communicate() dokumentasi:

Perhatikan bahwa jika Anda ingin mengirim data ke stdin proses, Anda harus membuat objek Popen dengan stdin = PIPE. Demikian pula, untuk mendapatkan apa pun selain Tidak Ada dalam tuple hasil, Anda perlu memberikan stdout = PIPE dan / atau stderr = PIPE juga.

Mengganti os.popen *

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

Peringatan Gunakan berkomunikasi () daripada stdin.write (), stdout.read () atau stderr.read () untuk menghindari kebuntuan karena salah satu penyangga pipa OS lainnya mengisi dan memblokir proses anak.

Jadi contoh Anda dapat ditulis sebagai berikut:

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

Pada versi Python 3 saat ini, Anda bisa menggunakan subprocess.run, untuk meneruskan input sebagai string ke perintah eksternal dan mendapatkan status keluarnya, dan hasilnya sebagai string kembali dalam satu panggilan:

#!/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
sumber
3
Saya melewatkan peringatan itu. Saya senang saya bertanya (meskipun saya pikir saya punya jawabannya).
Daryl Spitzer
11
Ini BUKAN solusi yang baik. Khususnya, Anda tidak dapat secara asinkron memproses output p.stdout.readline jika Anda melakukan ini karena Anda harus menunggu seluruh stdout tiba. Memori juga tidak efisien.
OTZ
7
@OTZ Apa solusi yang lebih baik?
Nick T
11
@Nick T: " lebih baik " tergantung pada konteks. Hukum Newton baik untuk domain yang berlaku tetapi Anda membutuhkan relativitas khusus untuk merancang GPS. Lihat Non-blocking, baca pada subprocess.PIPE dengan python .
jfs
9
Tetapi perhatikan CATATAN untuk berkomunikasi : "jangan gunakan metode ini jika ukuran data besar atau tidak terbatas"
Owen
44

Saya menemukan solusi ini:

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

Apakah ada yang lebih baik?

Daryl Spitzer
sumber
25
@Moe: stdin.write()penggunaan tidak disarankan, p.communicate()harus digunakan. Lihat jawaban saya.
jfs
11
Per dokumentasi subproses: Peringatan - Gunakan komunikasi () daripada .stdin.write, .stdout.read atau .stderr.read untuk menghindari kebuntuan karena buffer pipa OS lainnya mengisi dan memblokir proses anak.
Jason Mock
1
Saya pikir ini adalah cara yang baik untuk melakukannya jika Anda yakin stdout / err Anda tidak akan pernah terisi (misalnya, itu akan berupa file, atau utas lainnya memakannya) dan Anda memiliki jumlah data yang tidak terbatas untuk dikirim ke stdin.
Lucretiel
1
Secara khusus, melakukannya dengan cara ini masih memastikan bahwa stdin ditutup, sehingga jika subproses adalah yang mengkonsumsi input selamanya, communicateakan menutup pipa dan membiarkan proses berakhir dengan anggun.
Lucretiel
@Lucretiel, jika proses mengkonsumsi stdin selamanya, maka mungkin itu masih bisa menulis stdout selamanya, jadi kita akan membutuhkan teknik yang sama sekali berbeda (tidak bisa read()dari itu, seperti communicate()halnya bahkan tanpa argumen).
Charles Duffy
25

Saya agak terkejut tidak ada yang menyarankan membuat pipa, yang menurut saya cara paling sederhana untuk meneruskan string ke stdin dari subproses:

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

subprocess.check_call(['your-command'], stdin=read)
Graham Christensen
sumber
2
The osdan subprocessdokumentasi berdua sepakat bahwa Anda harus memilih yang terakhir atas mantan. Ini adalah solusi lama yang memiliki penggantian standar (sedikit kurang ringkas); jawaban yang diterima mengutip dokumentasi terkait.
tripleee
1
Saya tidak yakin itu benar, tripleee. Dokumentasi yang dikutip mengatakan mengapa sulit untuk menggunakan pipa yang dibuat oleh proses, tetapi dalam solusi ini menciptakan pipa dan meneruskannya. Saya percaya itu menghindari masalah kebuntuan potensial mengelola pipa setelah proses sudah dimulai.
Graham Christensen
os.popen tidak digunakan lagi karena subproses
hd1
2
-1: itu mengarah ke jalan buntu, mungkin kehilangan data. Fungsi ini sudah disediakan oleh modul subproses. Gunakan alih-alih mengimplementasikan ulang dengan buruk (coba tulis nilai yang lebih besar dari buffer pipa OS)
jfs
Anda pantas mendapatkan pria terbaik, terima kasih atas solusi paling sederhana dan paling cerdas
Felipe Buccioni
21

Ada solusi yang bagus jika Anda menggunakan Python 3.4 atau lebih baik. Gunakan inputargumen alih-alih stdinargumen, yang menerima argumen byte:

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

Ini bekerja untuk check_outputdan run, tetapi tidak callatau check_calluntuk beberapa alasan.

Flimm
sumber
5
@vidstige Anda benar, itu aneh. Saya akan mempertimbangkan untuk mengajukan ini sebagai bug Python, saya tidak melihat alasan yang bagus mengapa check_outputharus inputberdebat, tetapi tidak call.
Flimm
2
Ini adalah jawaban terbaik untuk Python 3.4+ (menggunakannya dalam Python 3.6). Memang tidak bekerja dengan check_calltetapi bekerja untuk run. Ini juga bekerja dengan input = string selama Anda melewati argumen encoding juga sesuai dengan dokumentasi.
Nikolaos Georgiou
13

Saya menggunakan python3 dan menemukan bahwa Anda perlu menyandikan string Anda sebelum Anda dapat meneruskannya ke stdin:

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)
qed
sumber
5
Anda tidak perlu secara khusus menyandikan input, itu hanya menginginkan objek seperti byte (misalnya b'something'). Ini akan mengembalikan err dan out sebagai byte juga. Jika Anda ingin menghindari ini, Anda bisa meneruskan universal_newlines=Trueke Popen. Maka ia akan menerima input sebagai str dan akan mengembalikan err / out sebagai str juga.
Enam
2
Namun berhati-hatilah, universal_newlines=Truejuga akan mengonversi baris baru Anda agar sesuai dengan sistem Anda
Nacht
1
Jika Anda menggunakan Python 3, lihat jawaban saya untuk solusi yang lebih nyaman.
Flimm
12

Rupanya objek cStringIO.StringIO tidak dukun cukup dekat ke file bebek sesuai subprocess.Popen

Aku takut tidak. Pipa adalah konsep OS tingkat rendah, sehingga benar-benar membutuhkan objek file yang diwakili oleh deskriptor file tingkat OS. Solusi Anda adalah yang benar.

Dan Lenski
sumber
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
sumber
3
fyi, tempfile.SpooledTemporaryFile .__ doc__ mengatakan: Pembungkus file sementara, khusus untuk beralih dari StringIO ke file nyata ketika melebihi ukuran tertentu atau ketika fileno diperlukan.
Doug F
5

Berhati-hatilah yang Popen.communicate(input=s)mungkin memberi Anda masalah jika sterlalu besar, karena tampaknya proses induk akan buffer sebelum forking subproses anak, yang berarti perlu "digunakan memori" dua kali lebih banyak pada saat itu (setidaknya sesuai dengan penjelasan "di bawah tenda" dan dokumentasi terkait ditemukan di sini ). Dalam kasus khusus saya, sadalah generator yang pertama kali sepenuhnya diperluas dan baru kemudian ditulis untuk stdinsehingga proses induk sangat besar sebelum anak itu lahir, dan tidak ada memori yang tersisa untuk memotongnya:

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
sumber
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
sumber
4
Karena shell=Truebegitu umum digunakan tanpa alasan yang baik, dan ini adalah pertanyaan yang populer, izinkan saya menunjukkan bahwa ada banyak situasi di mana Popen(['cmd', 'with', 'args'])jelas lebih baik daripada Popen('cmd with args', shell=True)dan memiliki shell memecah perintah dan argumen menjadi token, tetapi tidak sebaliknya memberikan apa pun berguna, sambil menambahkan sejumlah kompleksitas dan dengan demikian juga menyerang permukaan.
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
sumber
1

Pada Python 3.7+ lakukan ini:

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

dan Anda mungkin ingin menambahkan capture_output=Trueuntuk mendapatkan output dari menjalankan perintah sebagai string.

Pada versi Python yang lebih lama, ganti text=Truedengan universal_newlines=True:

subprocess.run(["grep", "f"], universal_newlines=True, input=my_data)
Boris
sumber