Как передать строку в 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)
Другие вопросы по тегам