Если я сделаю следующее:
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. Как мне обойти это?
сообщение в блоге содержит несколько ошибок, например, самый первый пример кода: call(['ls', '-1'], shell=True) неверно. Я рекомендую вместо этого прочитать общие вопросы из описания тега подпроцесса. В частности, Почему subprocess.Popen не работает, если аргументы - это последовательность? объясняет, почему call(['ls', '-1'], shell=True) ошибочен. Я помню, как оставлял комментарии под сообщением в блоге, но по какой-то причине я их сейчас не вижу.
Для более нового subprocess.run см. stackoverflow.com/questions/48752152/…






Я нашел обходной путь:
>>> 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()
Есть лучший?
@Moe: использование stdin.write() не рекомендуется, следует использовать p.communicate(). Смотрите мой ответ.
Согласно документации подпроцесса: Предупреждение - используйте messages (), а не .stdin.write, .stdout.read или .stderr.read, чтобы избежать взаимоблокировок из-за заполнения любого другого буфера канала ОС и блокировки дочернего процесса.
Я думаю, что это хороший способ сделать это, если вы уверены, что ваш stdout / err никогда не заполнится (например, он идет в файл или другой поток его ест) и у вас неограниченный объем данных для отправки на стандартный ввод.
В частности, выполнение этого способа по-прежнему гарантирует, что stdin закрыт, так что, если подпроцесс - это тот, который потребляет ввод навсегда, communicate закроет канал и позволит процессу корректно завершиться.
@Lucretiel, если процесс потребляет stdin навсегда, то, по-видимому, он все еще может писать stdout вечно, поэтому нам потребуются совершенно разные методы (не может read() от него, как communicate() даже без аргументов).
@Lucretiel, в любом случае, чтобы избежать взаимоблокировок, вам нужно, чтобы p.stdin.write() выполнялся в другом потоке, и в этом ответе не показаны необходимые методы. p.stdin.write() может иметь место, но его место не в ответе, который был бы настолько коротким и простым, чтобы не продемонстрировать, как его использовать безопасно.
Apparently a cStringIO.StringIO object doesn't quack close enough to a file duck to suit subprocess.Popen
Я не боюсь. Канал - это низкоуровневая концепция ОС, поэтому для него абсолютно необходим файловый объект, представленный файловым дескриптором уровня ОС. Ваш обходной путь правильный.
Документация Popen.communicate():
Note that if you want to send data to the process’s stdin, you need to create the Popen object with stdin=PIPE. Similarly, to get anything other than None in the result tuple, you need to give stdout=PIPE and/or stderr=PIPE too.
Replacing os.popen*
pipe = os.popen(cmd, 'w', bufsize)
# ==>
pipe = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE).stdin
Warning Use communicate() rather than stdin.write(), stdout.read() or stderr.read() to avoid deadlocks due to any of the other OS pipe buffers filling up and blocking the child process.
Итак, ваш пример можно было бы записать следующим образом:
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) вы можете использовать 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
# ->
Я пропустил это предупреждение. Я рад, что спросил (хотя я думал, что знаю ответ).
Это НЕ хорошее решение. В частности, вы не можете асинхронно обрабатывать вывод p.stdout.readline, если вы это сделаете, поскольку вам придется ждать прибытия всего stdout. Это также неэффективно с памятью.
@OTZ Какое решение лучше?
@Nick T: "лучше" зависит от контекста. Законы Ньютона хороши для той области, в которой они применимы, но для разработки GPS нужна специальная теория относительности. См. Неблокирующее чтение в подпроцессе. PIPE в python.
Но обратите внимание на ПРИМЕЧАНИЕ для общаться: «не используйте этот метод, если размер данных большой или неограниченный»
Может ли кто-нибудь объяснить, что делает каждый шаг команд, чтобы их можно было применить к другим проблемам?
@ LP_640 subprocess.Popen может применяться для решения очень многих задач. Вы можете начать с общие проблемы, связанные в описании тега подпроцесса.
@ J.F.Sebastian Но когда мы пишем, p.communicate(input=b'one\n'). Я знаю, что мы пишем в stdin дочернего процесса. Но записывает ли родительский процесс через stdout в stdin дочернего процесса? Не могли бы вы объяснить в своем ответе, как это?
@overexchange print в коде выводится на стандартный вывод Python. Это не связано с передачей ввода в подпроцесс в виде строки байтов. stdin, stdout, stderr в grep не имеют ничего общего с stdin, stdout, stderr в Python в этом примере. Сюда перенаправляются все стандартные потоки grep. .communicate () использует grep stdin, stdout, stderr безопасным способом (он может использовать потоки, async. io под капотом. Он скрывает сложность: вы просто передаете строку, и она доставляется дочернему элементу через канал для вас и соответствующий вывод считывается из другого канала, который подключен к stdout grep, и возвращается).
@ J.F.Sebastian Я знаю, что это stdin, stdout, stderr подпроцесса grep, когда я имею в виду те дескрипторы в Popen (grep, stdout, stderr) .communicate (b'something '). Мой вопрос: как родительский процесс отправляет данные (b'something') на stdin grep?
@overexchange stdin = PIPE в коде создает канал. Любые данные, записанные python на одном конце канала, могут быть прочитаны процессом grep на другом конце (он подключен к stdin grep, grep просто читает из своего stdin). python видит свой конец конвейера как файловый объект p.stdin со всеми обычными методами: .write (), .flush (), .fileno (), .close ().
Для использования аргумента input с subprocess.run() вам понадобится python 3.6. Если вы сделаете это, старые версии python3 будут работать: p = run(['grep', 'f'], stdout=PIPE, input=some_string.encode('ascii'))
@TaborKelly: 1 - примечание: вам не нужен .encode() - код использует параметр encoding 3 - «текущая версия Python 3» относится к Python 3.6. Сейчас это Python 3.7.
Извините, у меня есть опечатка. Для использования encoding вам понадобится Python 3.6, мой пример работает на Python 3.5. Для использования аргумента encoding с subprocess.run() вам понадобится python 3.6. Это очень удобно, поскольку еще не все работают с Python 3.6, например, Debian Stable работает на Python 3.5.
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)
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()
fyi, tempfile.SpooledTemporaryFile .__ doc__ говорит: Оболочка временного файла, специализированная для переключения с StringIO на реальный файл, когда он превышает определенный размер или когда требуется fileno.
"""
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()
Поскольку shell=True так часто используется без уважительной причины, и это популярный вопрос, позвольте мне указать, что во многих ситуациях Popen(['cmd', 'with', 'args']) явно лучше, чем Popen('cmd with args', shell=True), и оболочка разбивает команду и аргументы на токены, но не в противном случае предоставляя что-либо полезное, добавляя при этом значительную сложность и, следовательно, также поверхность для атак.
Помните, что 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
Я использую 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)
Вам не нужно специально кодировать ввод, ему просто нужен байтовый объект (например, b'something'). Он также вернет err и out как байты. Если вы хотите избежать этого, вы можете передать universal_newlines=True в Popen. Затем он примет ввод как str и также вернет err / out как str.
Но будьте осторожны, universal_newlines=True также преобразует ваши символы новой строки в соответствии с вашей системой.
Если вы используете Python 3, см. мой ответ для еще более удобного решения.
Я немного удивлен, что никто не предложил создать канал, который, на мой взгляд, является самым простым способом передать строку в стандартный ввод подпроцесса:
read, write = os.pipe()
os.write(write, "stdin input here")
os.close(write)
subprocess.check_call(['your-command'], stdin=read)
Документация os и subprocess согласны с тем, что вы должны предпочесть последнее первому. Это устаревшее решение, имеющее (чуть менее краткую) стандартную замену; в принятом ответе цитируется соответствующая документация.
Я не уверен, что это правильно, тройняшка. В процитированной документации объясняется, почему трудно использовать каналы, созданные в процессе, но в этом решении он создает канал и передает его. Я считаю, что это позволяет избежать потенциальных проблем с тупиком при управлении каналами после того, как процесс уже запущен.
os.popen устарел в пользу подпроцесса
-1: ведет в тупик, может потерять данные. Эта функциональность уже предоставляется модулем подпроцесса. Используйте его вместо того, чтобы переопределить его плохо (попробуйте написать значение, которое больше, чем буфер канала ОС)
Вы достойны самого лучшего хорошего человека, спасибо за самое простое и умное решение
@tripleee реализация каналов в модуле подпроцесса до смешного плохая, и ее невозможно контролировать. Вы даже не можете получить информацию о размере встроенного буфера, не говоря уже о том, что вы не можете сказать ему, каковы концы для чтения и записи канала, а также вы не можете изменить встроенный буфер. Короче говоря: трубы подпроцесса - мусор. Не используйте их.
Есть прекрасное решение, если вы используете Python 3.4 или выше. Используйте аргумент input вместо аргумента stdin, который принимает аргумент в байтах:
output = subprocess.check_output(
["sed", "s/foo/bar/"],
input=b"foo",
)
Это работает для check_output и run, но по какой-то причине не работает для call или check_call.
@vidstige Вы правы, это странно. Я бы рассмотрел регистрацию этого как ошибки Python, я не вижу веских причин, по которым check_output должен иметь аргумент input, но не call.
Это лучший ответ для Python 3.4+ (используя его в Python 3.6). Он действительно не работает с check_call, но работает с run. Он также работает с input = string, если вы также передаете аргумент кодировки в соответствии с документацией.
В 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)
capture_output не работает на новом питоне?
Это так, но вы должны использовать subprocess.run для всего, прочтите документацию
Вместо того, чтобы оспаривать свой ответ с его удалением, я добавляю его в качестве комментария ... Рекомендуемая литература: Сообщение в блоге Дуга Хеллмана о модуле Python недели о подпроцессе.