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

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

import subprocess
from cStringIO import StringIO
subprocess.Popen(['grep','f'],stdout=subprocess.PIPE,stdin=StringIO('onentwonthreenfournfivensixn')).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 не крякает достаточно близко к утке файла, чтобы удовлетворить подпроцесс.К popen. Как мне обойти это?

10 ответов


Popen.communicate() документы:

обратите внимание, что если вы хотите отправить данные процесс stdin, вам нужно создайте объект Popen с помощью устройства stdin=труба. Аналогично, чтобы получить что-нибудь кроме None в результирующем кортеже, вам нужно дать stdout=PIPE и / или stderr=труба тоже.

замена ОС.к popen*

    pipe = os.popen(cmd, 'w', bufsize)
    # ==>
    pipe = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE).stdin

предупреждение используйте communicate (), а не стандартный ввод.писать(), стандартный вывод.читать или поток stderr.читать (), чтобы избежать блокировки из-за к любому из других буферов канала OS заполнение и блокирование ребенка процесс.

так что ваш пример может быть записан следующим образом:

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

есть ли лучший?


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

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

subprocess.check_call(['your-command'], stdin=read)

Я использую 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)

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

: -)

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


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

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

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

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


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)