Как передать строку в 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. Как мне обойти это?
12 ответов
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
# ->
Я понял этот обходной путь:
>>> 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()
Есть ли лучший?
Есть прекрасное решение, если вы используете Python 3.4 или выше. Использовать input
аргумент вместо stdin
аргумент, который принимает байтовый аргумент:
output = subprocess.check_output(
["sed", "s/foo/bar/"],
input=b"foo",
)
Я немного удивлен, что никто не предложил создать канал, который, на мой взгляд, является самым простым способом передачи строки в стандартный поток подпроцесса:
read, write = os.pipe()
os.write(write, "stdin input here")
os.close(write)
subprocess.check_call(['your-command'], stdin=read)
Я использую 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)
"По-видимому, объект cStringIO.StringIO не крякает достаточно близко к файловой утке, чтобы удовлетворить subprocess.Popen"
:-)
Боюсь, что нет. Канал представляет собой низкоуровневую концепцию ОС, поэтому ему абсолютно необходим файловый объект, представленный дескриптором файла на уровне ОС. Ваш обходной путь правильный.
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()
"""
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()
В 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)
Остерегайтесь этого 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
Это перебор для
grep
, но в своих путешествиях я узнал о команде Linux
expect
, и библиотека Python
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 ('noah@example.com')
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.
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)