Як передати рядок у subprocess.Popen (використовуючи аргумент stdin)?


280

Якщо я виконую наступне:

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

Я отримав:

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'

Мабуть, об'єкт cStringIO.StringIO не збивається досить близько до файлової качки, щоб відповідати підпроцесу. Як мені це подолати?


3
Замість того, щоб сперечатися з моєю відповіддю, коли ця проблема була видалена, я додаю її як коментар ... Рекомендовано прочитати: Повідомлення про підпроцес в блозі Python-модуля Дуга Хеллмана .
Даріл Спітцер

3
публікація в блозі містить кілька помилок, наприклад, перший приклад коду:call(['ls', '-1'], shell=True) невірно. Я рекомендую замість цього прочитати поширені питання з опису тегів підпроцесу . Зокрема, чому subprocess.Popen не працює, коли аргументи є послідовними? пояснює, чому call(['ls', '-1'], shell=True)це неправильно. Я пам’ятаю, що залишав коментарі під дописом у блозі, але я чомусь не бачу їх.
jfs

Про новіші subprocess.runдивіться stackoverflow.com/questions/48752152/…
Борис

Відповіді:


326

Popen.communicate() документація:

Зауважте, що якщо ви хочете надіслати дані до stdin процесу, вам потрібно створити об'єкт Popen за допомогою stdin = PIPE. Аналогічно, щоб отримати що-небудь інше, ніж None, в результатах кортежу, вам також потрібно вказати stdout = PIPE та / або stderr = PIPE.

Заміна os.popen *

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

Попередження Використовуйте спілкування (), а не stdin.write (), stdout.read () або stderr.read (), щоб уникнути тупикових ситуацій через заповнення будь-якого іншого буфера труб ОС і блокування дочірнього процесу.

Тож ваш приклад можна записати так:

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

У поточній версії Python 3 ви можете використовувати subprocess.runдля передачі введення як рядок до зовнішньої команди та отримання її статусу виходу, а його вихід у вигляді рядка назад за один виклик:

#!/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
Я пропустив це попередження. Я радий, що запитав (хоча я думав, що маю відповідь).
Даріл Спітцер

11
Це НЕ вдале рішення. Зокрема, ви не можете асинхронно обробити вихід p.stdout.readline, якщо це зробити, оскільки вам доведеться чекати, поки прийде весь stdout. Це також неефективно.
ОТЗ

7
@OTZ Що краще рішення?
Нік Т

11
@ Nick T: " краще " залежить від контексту. Закони Ньютона корисні для тієї сфери, в якій вони застосовуються, але для створення GPS потрібна спеціальна відносність. Див. Розділ Неблокування, прочитане на підпроцесі.PIPE в python .
jfs

9
Але зверніть увагу на ПРИМІТК для спілкування : "не використовуйте цей метод, якщо розмір даних великий чи необмежений"
Оуен

44

Я вирішив це рішення:

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

Чи є кращий?


25
@Moe: stdin.write()використання не рекомендується, p.communicate()слід використовувати. Дивіться мою відповідь.
jfs

11
Згідно з документацією про підпроцес: Попередження - Використовуйте, а не .stdin.write, .stdout.read або .stderr.read, щоб уникнути тупикових ситуацій через заповнення будь-якого з інших буферів труб ОС і блокування дочірнього процесу.
Джейсон Мок

1
Я думаю, що це хороший спосіб зробити це, якщо ви впевнені, що ваш stdout / err ніколи не заповнюватиметься (наприклад, він збирається у файл, або інша нитка їсть його), і у вас є необмежений обсяг даних відправити в stdin.
Лукретьєль

1
Зокрема, таким чином все-таки забезпечується закриття stdin, так що якщо підпроцесори є тим, хто споживає вхід назавжди, communicateто труба закриє і дозволить процес закінчитися граціозно.
Лукретьєль

@Lucretiel, якщо процес споживає stdin назавжди, то, імовірно, він все ще може писати stdout назавжди, тому нам знадобляться абсолютно різні методи в усьому масштабі (не може read()з цього, як communicate()і навіть без аргументів).
Чарльз Даффі

25

Я трохи здивований, що ніхто не запропонував створити трубу, що, на мій погляд, є найпростішим способом передати рядок до stdin підпроцесу:

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

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

2
osІ subprocessдокументації обидва згодні з тим , що ви повинні віддавати перевагу останній над першою. Це застаріле рішення, яке має (трохи менш стисну) стандартну заміну; прийнята відповідь цитує відповідну документацію.
трійчатка

1
Я не впевнений, що це правильно, трійка. В цитованій документації сказано, чому важко використовувати труби, створені процесом, але в цьому рішенні він створює трубу і передає її. Я вважаю, що це дозволяє уникнути потенційних проблем з тупиком управління трубами після того, як процес вже розпочався.
Грем Крістенсен

os.popen застаріло на користь підпроцесу
hd1

2
-1: це призводить до тупикової ситуації, може втратити дані. Цю функціональність вже надає модуль підпроцесу. Використовуйте його замість того, щоб погано виконувати його (спробуйте записати значення, яке більше, ніж буфер труб для ОС)
jfs

Ви заслуговуєте на найкращого доброго чоловіка, дякую вам за найпростіше і
розумніше

21

Є прекрасне рішення, якщо ви використовуєте Python 3.4 або вище. Використовуйте inputзамість stdinаргументу, який приймає аргумент байтів:

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

Це працює check_outputі run, але не callабо check_callз якоїсь - то причини.


5
@vidstige Ви маєте рацію, це дивно. Я вважаю, що подати це як помилку Python, я не бачу вагомих причин, чому check_outputслід мати inputаргумент, але ні call.
Flimm

2
Це найкраща відповідь для Python 3.4+ (використовуючи його в Python 3.6). Він справді не працює, check_callале працює run. Він також працює з input = string, поки ви передаєте аргумент кодування відповідно до документації.
Ніколаос Георгіу

13

Я використовую python3 і з'ясував, що вам потрібно кодувати рядок, перш ніж ви зможете передати її в 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)

5
Вам не потрібно спеціально кодувати вхід, він просто бажає байтовий об'єкт (наприклад b'something'). Це також поверне помилку і як байти. Якщо ви хочете цього уникнути, можете перейти universal_newlines=Trueдо Popen. Тоді він прийме введення як str та поверне err / out як str також.
Шість

2
Але будьте обережні, universal_newlines=Trueтакож перетворите ваші нові рядки відповідно до вашої системи
Нахт - Відновіть Моніку

1
Якщо ви використовуєте Python 3, дивіться мою відповідь щодо ще зручнішого рішення.
Flimm

12

Мабуть, об'єкт cStringIO.StringIO не збивається досить близько до файлової качки, щоб відповідати підпроцесу.

Боюся, що немає. Труба - це концепція ОС низького рівня, тому вона абсолютно потребує файлового об'єкта, який представлений дескриптором файлів на рівні ОС. Ваш спосіб вирішення правильний.


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__ говорить: Тимчасовий обгортчик файлів, спеціалізований для переходу з StringIO в реальний файл, коли він перевищує певний розмір або коли потрібен файл.
Doug F

5

Будьте обережні, що Popen.communicate(input=s)може доставити вам проблеми, якщо sвін занадто великий, тому що, мабуть, батьківський процес захистить його перед тим, як форсувати дочірній підпроцес. та пов’язану тут документацію ). У моєму конкретному випадку це sбув генератор, який спочатку повністю розширився і лише потім був записаний stdinтак, щоб батьківський процес був величезним безпосередньо перед тим, як народилася дитина, і не залишилося пам'яті, щоб розщедрити його:

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
Оскільки shell=Trueтак часто використовується без поважних причин, і це популярне питання, дозвольте зазначити, що існує маса ситуацій, коли Popen(['cmd', 'with', 'args'])рішуче краще, ніж Popen('cmd with args', shell=True)і оболонка розбиває команду та аргументи на лексеми, але інакше не надаючи нічого корисно, додаючи при цьому значну кількість складності і тим самим також атакуючи поверхню.
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)

1

На Python 3.7+ зробіть це:

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

і ви, ймовірно, захочете додати, capture_output=Trueщоб отримати результат виконання команди у вигляді рядка.

У старих версіях Python замініть text=Trueна universal_newlines=True:

subprocess.run(["grep", "f"], universal_newlines=True, input=my_data)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.