Как передать строку в subprocess.Popen (используя аргумент stdin)?

281

Если я сделаю следующее:

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 не крякает достаточно близко к файловой утке, чтобы удовлетворить subprocess.Popen. Как мне обойти это?

Дэрил Спитцер
источник
3
Вместо того, чтобы оспаривать мой ответ с этим удалением, я добавляю его в качестве комментария ... Рекомендуемое чтение: пост в блоге Дуга Хеллмана «Модуль недели Python» по подпроцессу .
Дэрил Спитцер
3
сообщение в блоге содержит несколько ошибок, например, самый первый пример кода:call(['ls', '-1'], shell=True) неверно. Вместо этого я рекомендую прочитать общие вопросы из описания тегов подпроцесса . В частности, почему subprocess.Popen не работает, когда args является последовательностью? объясняет почему call(['ls', '-1'], shell=True)не так. Я помню, как оставлял комментарии под постом в блоге, но я почему-то их сейчас не вижу.
Jfs
Для более нового subprocess.runсм. Stackoverflow.com/questions/48752152/…
Борис

Ответы:

326

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

Обратите внимание, что если вы хотите отправить данные в стандартный процесс, вам нужно создать объект 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

Предупреждение. Используйте connect () вместо 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
# -> 
JFS
источник
3
Я пропустил это предупреждение. Я рад, что спросил (хотя я думал, что у меня есть ответ).
Дэрил Спитцер
11
Это НЕ хорошее решение. В частности, вы не можете асинхронно обрабатывать вывод p.stdout.readline, если вы делаете это, так как вам придется ждать, пока весь stdout не прибудет. Это также неэффективно для памяти.
OTZ
7
@OTZ Какое решение лучше?
Ник Т
11
@ Ник Т: « лучше » зависит от контекста. Законы Ньютона хороши для той области, к которой они применимы, но вам нужна особая теория относительности для проектирования GPS. Смотрите Неблокирующее чтение для подпроцесса. ТРУБА в 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
@ Мое: stdin.write()использование не рекомендуется, p.communicate()следует использовать. Смотри мой ответ.
JFS
11
В документации по подпроцессу: Предупреждение. Используйте communication (), а не .stdin.write, .stdout.read или .stderr.read, чтобы избежать взаимных блокировок из-за заполнения других буферов буфера ОС и блокировки дочернего процесса.
Джейсон Мок
1
Я думаю, что это хороший способ сделать это, если вы уверены, что ваш stdout / err никогда не заполнится (например, он идет в файл, или другой поток его ест), и у вас есть неограниченный объем данных быть отправленным на стандартный ввод.
Лукретиэль
1
В частности, выполнение этого способа по-прежнему гарантирует, что stdin закрыт, так что, если подпроцессы - это тот, который потребляет ввод навсегда, communicateон закроет канал и позволит процессу завершиться изящно.
Лукретиэль
@Lucretiel, если процесс использует stdin навсегда, то, по-видимому, он все еще может писать stdout навсегда, поэтому нам понадобятся совершенно разные методы повсюду (не может быть read(), как communicate()и без аргументов).
Чарльз Даффи
25

Я немного удивлен, что никто не предложил создать канал, который, на мой взгляд, является самым простым способом передачи строки в стандартный поток подпроцесса:

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

subprocess.check_call(['your-command'], stdin=read)
Грэм Кристенсен
источник
2
osИ subprocessдокументации оба согласны с тем , что вы должны отдавать предпочтение последней над первой. Это устаревшее решение, которое имеет (чуть менее лаконичную) стандартную замену; Принятый ответ цитирует соответствующую документацию.
tripleee
1
Я не уверен, что это правильно, tripleee. В цитируемой документации сказано, почему трудно использовать каналы, созданные процессом, но в этом решении он создает канал и передает его внутрь. Я считаю, что это позволяет избежать потенциальных тупиковых ситуаций управления каналами после того, как процесс уже запущен.
Грэм Кристенсен
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 и обнаружил, что вам нужно закодировать вашу строку, прежде чем вы сможете передать ее в стандартный ввод:

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
источник
5
Вам не нужно специально кодировать входные данные, он просто хочет подобный байту объект (например b'something'). Он также будет возвращать ошибки и байты. Если вы хотите избежать этого, вы можете перейти universal_newlines=Trueк Popen. Затем он примет ввод как str и вернет err / out как str.
6
2
Но будьте осторожны, universal_newlines=Trueтакже преобразует ваши новые строки в соответствии с вашей системой
Nacht
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.SpooledTeilitaryFile .__ doc__ говорит: временная оболочка файла, специализированная для переключения с StringIO на реальный файл, когда он превышает определенный размер или когда файл не требуется.
Даг Ф
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)
Борис
источник