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

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

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. Как мне обойти это?


person Daryl Spitzer    schedule 02.10.2008    source источник
comment
Вместо того, чтобы оспаривать свой ответ с его удалением, я добавляю его в качестве комментария ... Рекомендуемая литература: Сообщение в блоге Дуга Хеллмана о модуле Python недели о подпроцессе.   -  person Daryl Spitzer    schedule 19.06.2013
comment
сообщение в блоге содержит несколько ошибок, например, самый первый пример кода: call(['ls', '-1'], shell=True) неверен. Я рекомендую вместо этого прочитать общие вопросы из описания тега подпроцесса. В частности, Почему subprocess.Popen не работает, когда args - это последовательность? объясняет, почему call(['ls', '-1'], shell=True) неверно. Я помню, как оставлял комментарии под сообщением в блоге, но по какой-то причине я их сейчас не вижу.   -  person jfs    schedule 17.03.2016
comment
Для более нового subprocess.run см. stackoverflow.com/questions/48752152/   -  person Boris    schedule 27.12.2019


Ответы (11)


Документация по 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

Предупреждение. Используйте messages (), а не 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.5+ (3.6+ для encoding) вы можете использовать _5 _, чтобы передать ввод в виде строки внешней команде и получить ее статус выхода, а ее вывод в виде строки обратно за один вызов:

#!/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
# -> 
person jfs    schedule 03.10.2008
comment
Я пропустил это предупреждение. Я рад, что спросил (хотя я думал, что знаю ответ). - person Daryl Spitzer; 03.10.2008
comment
Это НЕ хорошее решение. В частности, вы не можете асинхронно обрабатывать вывод p.stdout.readline, если вы это сделаете, поскольку вам придется ждать прибытия всего stdout. Это также неэффективно с памятью. - person OTZ; 21.08.2010
comment
@OTZ Какое решение лучше? - person Nick T; 18.11.2010
comment
@Nick T: лучше зависит от контекста. Законы Ньютона хороши для той области, в которой они применимы, но для разработки GPS нужна специальная теория относительности. См. Неблокирующее чтение в подпроцессе.PIPE в python. - person jfs; 19.10.2011
comment
Но обратите внимание на ПРИМЕЧАНИЕ для общения: не используйте этот метод, если размер данных большой или неограниченный - person Owen; 22.01.2014
comment
Может ли кто-нибудь объяснить, что делает каждый шаг команд, чтобы их можно было применить к другим проблемам? - person LP_640; 25.02.2017
comment
@ LP_640 subprocess.Popen может быть применен к очень многим задачам. Вы можете начать с общих проблем, связанных в описании тега подпроцесса. - person jfs; 28.02.2017
comment
@ J.F.Sebastian Но когда мы пишем, p.communicate(input=b'one\n'). Я знаю, что мы пишем в stdin дочернего процесса. Но пишет ли родительский процесс через stdout дочерний процесс stdin? Не могли бы вы объяснить, как это, в Ваш ответ? - person overexchange; 14.07.2017
comment
@overexchange print в коде выводится на стандартный вывод Python. Это не связано с передачей ввода в подпроцесс в виде строки байтов. stdin, stdout, stderr в grep не имеют ничего общего с stdin, stdout, stderr в Python в этом примере. Сюда перенаправляются все стандартные потоки grep. .communicate () использует grep stdin, stdout, stderr безопасным способом (он может использовать потоки, async. io под капотом. Он скрывает сложность: вы просто передаете строку, и она доставляется дочернему элементу через канал для вас и соответствующий вывод считывается из другого канала, который подключен к stdout grep, и возвращается). - person jfs; 14.07.2017
comment
@ J.F.Sebastian Я знаю, что это stdin, stdout, stderr подпроцесса grep, когда я имею в виду те дескрипторы в Popen (grep, stdout, stderr) .communicate (b'something '). Мой вопрос: как родительский процесс отправляет данные (b'something') на стандартный ввод grep? - person overexchange; 14.07.2017
comment
@overexchange stdin = PIPE в коде создает канал. Любые данные, записанные python на одном конце канала, могут быть прочитаны процессом grep на другом конце (он подключен к stdin grep, grep просто читает из своего stdin). python видит свой конец канала как файловый объект p.stdin со всеми обычными методами: .write (), .flush (), .fileno (), .close (). - person jfs; 14.07.2017
comment
Вам нужен python 3.6, чтобы использовать input arg с subprocess.run(). Старые версии python3 будут работать, если вы сделаете это: p = run(['grep', 'f'], stdout=PIPE, input=some_string.encode('ascii')) - person TaborKelly; 03.08.2018
comment
@TaborKelly: 1- примечание: вам не нужен .encode() - в коде используется параметр encoding 3- текущая версия Python 3 относится к Python 3.6. Сейчас это Python 3.7. - person jfs; 03.08.2018
comment
Извините, у меня есть опечатка. Для использования encoding вам нужен Python 3.6, мой пример работает на Python 3.5. Вам нужен python 3.6, чтобы использовать encoding arg с subprocess.run(). Это очень удобно, поскольку еще не все работают с Python 3.6, например, Debian Stable работает на Python 3.5. - person TaborKelly; 03.08.2018

Я понял это обходное решение:

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

Есть лучший?

person Daryl Spitzer    schedule 02.10.2008
comment
@Moe: stdin.write() использование не рекомендуется, следует использовать p.communicate(). Смотрите мой ответ. - person jfs; 03.10.2008
comment
Согласно документации подпроцесса: Предупреждение - используйте messages (), а не .stdin.write, .stdout.read или .stderr.read, чтобы избежать взаимоблокировок из-за заполнения любого из других буферов канала ОС и блокировки дочернего процесса. - person Jason Mock; 26.08.2010
comment
Я думаю, что это хороший способ сделать это, если вы уверены, что ваш stdout / err никогда не заполнится (например, он идет в файл или другой поток его ест) и у вас неограниченный объем данных для отправки на стандартный ввод. - person Lucretiel; 09.05.2016
comment
В частности, выполнение этого способа по-прежнему гарантирует, что stdin закрыт, так что, если подпроцесс - это тот, который потребляет ввод навсегда, communicate закроет канал и позволит процессу корректно завершиться. - person Lucretiel; 09.05.2016
comment
@Lucretiel, если процесс потребляет stdin навсегда, то, по-видимому, он все еще может писать stdout вечно, поэтому нам потребуются совершенно разные методы (не могут read() от него, как communicate() даже без аргументов). - person Charles Duffy; 13.03.2020
comment
@Lucretiel, в любом случае, чтобы избежать взаимоблокировок, вам нужно, чтобы p.stdin.write() выполнялся в другом потоке, и в этом ответе не показаны необходимые методы. p.stdin.write() может иметь место, но его место не в ответе, который был бы настолько коротким и простым, чтобы не продемонстрировать, как его использовать безопасно. - person Charles Duffy; 13.03.2020

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

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

subprocess.check_call(['your-command'], stdin=read)
person Graham Christensen    schedule 02.11.2015
comment
И os, и subprocess документация согласны с тем, что вы должны предпочесть последнее первому. Это устаревшее решение, имеющее (чуть менее краткую) стандартную замену; в принятом ответе цитируется соответствующая документация. - person tripleee; 04.05.2016
comment
Я не уверен, что это правильно, тройняшка. В процитированной документации объясняется, почему трудно использовать каналы, созданные в процессе, но в этом решении он создает канал и передает его. Я считаю, что это позволяет избежать потенциальных проблем с тупиком при управлении каналами после того, как процесс уже запущен. - person Graham Christensen; 07.05.2016
comment
os.popen устарел в пользу подпроцесса - person hd1; 18.02.2017
comment
-1: ведет в тупик, может потерять данные. Эта функциональность уже предоставляется модулем подпроцесса. Используйте его вместо того, чтобы переопределить его плохо (попробуйте написать значение, которое больше, чем буфер канала ОС) - person jfs; 18.08.2017
comment
Вы достойны самого лучшего хорошего человека, спасибо за самое простое и умное решение - person Felipe Buccioni; 31.10.2019
comment
@tripleee реализация каналов в модуле подпроцесса до смешного плохая, и ее невозможно контролировать. Вы даже не можете получить информацию о размере встроенного буфера, не говоря уже о том, что вы не можете сказать ему, каковы концы для чтения и записи канала, а также вы не можете изменить встроенный буфер. Короче говоря: трубы подпроцесса - мусор. Не используйте их. - person wvxvw; 27.12.2020

Есть прекрасное решение, если вы используете Python 3.4 или выше. Используйте аргумент input вместо аргумента stdin, который принимает байтовый аргумент:

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

Это работает для check_output и _ 5_, но не _ 6_ или _ 7_ по какой-то причине.

person Flimm    schedule 08.12.2016
comment
@vidstige Вы правы, это странно. Я бы рассмотрел это как ошибку Python, я не вижу веских причин, почему check_output должен иметь аргумент input, но не call. - person Flimm; 05.10.2017
comment
Это лучший ответ для Python 3.4+ (используя его в Python 3.6). Это действительно не работает с check_call, но работает с run. Он также работает с input = string, если вы также передаете аргумент кодировки в соответствии с документацией. - person Nikolaos Georgiou; 22.02.2019

Я использую 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)
person qed    schedule 27.07.2014
comment
Вам не нужно специально кодировать ввод, ему просто нужен байтовый объект (например, b'something'). Он также вернет err и out как байты. Если вы хотите избежать этого, вы можете передать universal_newlines=True в Popen. Затем он примет ввод как str и также вернет err / out как str. - person Six; 17.01.2016
comment
Но будьте осторожны, universal_newlines=True также преобразует ваши символы новой строки в соответствие с вашей системой - person Nacht; 25.02.2016
comment
Если вы используете Python 3, см. мой ответ для еще более удобного решения. - person Flimm; 08.12.2016

По-видимому, объект cStringIO.StringIO не крякает достаточно близко к файловому утку, чтобы соответствовать подпроцессу.

Я не боюсь. Канал представляет собой концепцию ОС нижнего уровня, поэтому для него абсолютно необходим файловый объект, представленный файловым дескриптором уровня ОС. Ваш обходной путь правильный.

person Dan Lenski    schedule 02.10.2008

Помните, что 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

person Lord Henry Wotton    schedule 19.05.2014

В 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)
person Boris    schedule 27.12.2019
comment
capture_output не работает на новом питоне? - person Rainb; 03.12.2020

Это перебор для grep, но в ходе своих путешествий я узнал о команде Linux expect и библиотеке python pexpect

  • expect: диалог с интерактивными программами
  • pexpect: модуль Python для создания дочерних приложений; контролировать их; и реагируя на ожидаемые закономерности в их результатах.
import pexpect
child = pexpect.spawn('grep f', timeout=10)
child.sendline('text to match')
print(child.before)

Работа с интерактивными приложениями оболочки, такими как ftp, с pexpect

import pexpect
child = pexpect.spawn ('ftp ftp.openbsd.org')
child.expect ('Name .*: ')
child.sendline ('anonymous')
child.expect ('Password:')
child.sendline ('[email protected]')
child.expect ('ftp> ')
child.sendline ('ls /pub/OpenBSD/')
child.expect ('ftp> ')
print child.before   # Print the result of the ls command.
child.interact()     # Give control of the child to the user.
person Ben DeMott    schedule 22.03.2021

person    schedule
comment
fyi, tempfile.SpooledTemporaryFile .__ doc__ говорит: Оболочка временного файла, специализированная для переключения с StringIO на реальный файл, когда он превышает определенный размер или когда требуется fileno. - person Doug F; 10.08.2013

person    schedule
comment
Поскольку shell=True так часто используется без уважительной причины, и это популярный вопрос, позвольте мне указать, что во многих ситуациях Popen(['cmd', 'with', 'args']) явно лучше, чем Popen('cmd with args', shell=True) и когда оболочка разбивает команду и аргументы на токены, но не в противном случае предоставляя что-нибудь полезное, добавляя при этом значительную сложность и, таким образом, также поверхность для атак. - person tripleee; 29.10.2014