Como passo uma string no subprocess.Popen (usando o argumento stdin)?

280

Se eu fizer o seguinte:

import subprocess
from cStringIO import StringIO
subprocess.Popen(['grep','f'],stdout=subprocess.PIPE,stdin=StringIO('one\ntwo\nthree\nfour\nfive\nsix\n')).communicate()[0]

Eu recebo:

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'

Aparentemente, um objeto cStringIO.StringIO não fica quack perto o suficiente de um arquivo duck para se adequar ao subprocesso.Popen. Como faço para resolver isso?

Daryl Spitzer
fonte
3
Em vez de contestar minha resposta com essa exclusão, estou adicionando-a como um comentário ... Leitura recomendada: Postagem no blog Módulo da semana Python de Doug Hellmann no subprocesso .
Daryl Spitzer
3
a postagem do blog contém vários erros, por exemplo, o primeiro exemplo de código:call(['ls', '-1'], shell=True) está incorreto. Eu recomendo ler perguntas comuns da descrição da tag do subprocesso . Em particular, por que o subprocess.Popen não funciona quando args é uma sequência? explica por que call(['ls', '-1'], shell=True)está errado. Lembro-me de deixar comentários sob a postagem do blog, mas não os vejo agora por algum motivo.
JFS
Para os mais recentes, subprocess.runconsulte stackoverflow.com/questions/48752152/…
Boris

Respostas:

326

Popen.communicate() documentação:

Observe que se você deseja enviar dados para o stdin do processo, é necessário criar o objeto Popen com stdin = PIPE. Da mesma forma, para obter algo diferente de None na tupla de resultado, você também deve fornecer stdout = PIPE e / ou stderr = PIPE.

Substituindo os.popen *

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

Aviso Use communic () em vez de stdin.write (), stdout.read () ou stderr.read () para evitar bloqueios devido a qualquer um dos outros buffers de pipe do sistema operacional enchendo e bloqueando o processo filho.

Portanto, seu exemplo pode ser escrito da seguinte maneira:

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
# ->

Na versão atual do Python 3, você pode usar subprocess.runpara passar a entrada como uma string para um comando externo e obter seu status de saída e sua saída como uma string de volta em uma chamada:

#!/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
# -> 
jfs
fonte
3
Eu perdi esse aviso. Fico feliz por ter perguntado (apesar de achar que tinha a resposta).
Daryl Spitzer
11
Esta não é uma boa solução. Em particular, você não pode processar de forma assíncrona a saída p.stdout.readline se fizer isso, pois terá que aguardar a chegada de todo o stdout. Também é ineficiente em memória.
OTZ
7
@OTZ Qual é a melhor solução?
Nick T
11
@ Nick T: " melhor " depende do contexto. As leis de Newton são boas para o domínio em que são aplicáveis, mas você precisa de uma relatividade especial para projetar o GPS. Consulte Leitura sem bloqueio em um subprocesso.PIPE em python .
JFS
9
Mas observe a NOTA para comunicar : "não use esse método se o tamanho dos dados for grande ou ilimitado"
Owen
44

Eu descobri esta solução alternativa:

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

Existe um melhor?

Daryl Spitzer
fonte
25
@ Moe: o stdin.write()uso é desencorajado, p.communicate()deve ser usado. Veja minha resposta.
JFS
11
De acordo com a documentação do subprocesso: Aviso - Use communic () em vez de .stdin.write, .stdout.read ou .stderr.read para evitar bloqueios devido a qualquer um dos outros buffers de pipe do sistema operacional enchendo e bloqueando o processo filho.
Jason Mock
1
Eu acho que essa é uma boa maneira de fazer isso, se você estiver confiante de que seu stdout / err nunca será preenchido (por exemplo, ele vai para um arquivo ou outro encadeamento o está consumindo) e você tem uma quantidade ilimitada de dados para ser enviado ao stdin.
Lucretiel
1
Em particular, fazê-lo dessa maneira ainda garante que o stdin seja fechado, de modo que, se o subprocesso é aquele que consome a entrada para sempre, communicateele fecha o canal e permite que o processo termine normalmente.
Lucretiel
@ Lucretiel, se o processo consumir stdin para sempre, presumivelmente ele ainda pode escrever stdout para sempre, por isso precisaríamos de técnicas completamente diferentes o tempo todo (não pode, read()com isso, communicate()mesmo sem argumentos).
Charles Duffy
25

Estou um pouco surpreso que ninguém tenha sugerido a criação de um pipe, que é, na minha opinião, a maneira mais simples de passar uma string para o stdin de um subprocesso:

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

subprocess.check_call(['your-command'], stdin=read)
Graham Christensen
fonte
2
A osea subprocessdocumentação ambos concordam que você deve preferir o segundo sobre o primeiro. Esta é uma solução herdada que possui uma substituição padrão (um pouco menos concisa); a resposta aceita cita a documentação pertinente.
Tripleee
1
Não sei se isso está correto, tripla. A documentação citada diz por que é difícil usar os tubos criados pelo processo, mas nesta solução ele cria um tubo e o passa. Acredito que evite os possíveis problemas de conflito de gerenciamento dos tubos após o processo já ter sido iniciado.
Graham Christensen
os.popen é depreciado em favor do subprocesso
HD1
2
-1: leva ao impasse, pode perder dados. Essa funcionalidade já é fornecida pelo módulo de subprocesso. Usá-lo em vez de reimplementar-lo mal (tentar escrever um valor que é maior do que um tampão tubulação OS)
JFS
Você merece o melhor homem bom, obrigado pela solução mais simples e mais inteligente
Felipe Buccioni
21

Existe uma solução bonita se você estiver usando o Python 3.4 ou melhor. Use o inputargumento em vez do stdinargumento, que aceita um argumento de bytes:

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

Isso funciona para check_outpute run, mas não callou check_callpor algum motivo.

Flimm
fonte
5
@vidstige Você está certo, isso é estranho. Eu consideraria arquivar isso como um bug do Python, não vejo nenhum bom motivo para check_outputter um inputargumento, mas não call.
Flimm # 5/17
2
Esta é a melhor resposta para o Python 3.4+ (usando-o no Python 3.6). Realmente não funciona, check_callmas funciona para run. Também funciona com input = string, desde que você passe um argumento de codificação também de acordo com a documentação.
Nikolaos Georgiou
13

Estou usando python3 e descobri que você precisa codificar sua string antes de poder passá-la para o 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)
qed
fonte
5
Você não precisa codificar especificamente a entrada, apenas deseja um objeto semelhante a bytes (por exemplo b'something'). Ele retornará err e out como bytes também. Se você quiser evitar isso, pode passar universal_newlines=Truepara Popen. Então ele aceitará a entrada como str e retornará err / out como str também.
Seis
2
Mas cuidado, universal_newlines=Truetambém converterá suas novas linhas para que correspondam ao seu sistema #
Nacht - Reinstate Monica
1
Se você estiver usando o Python 3, veja minha resposta para uma solução ainda mais conveniente.
Flimm
12

Aparentemente, um objeto cStringIO.StringIO não é quack perto o suficiente de um arquivo duck para se adequar ao subprocesso.

Receio que não. O canal é um conceito de SO de baixo nível, portanto, requer absolutamente um objeto de arquivo representado por um descritor de arquivo no nível de SO. Sua solução alternativa é a correta.

Dan Lenski
fonte
7
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()
Michael Waddell
fonte
3
fyi, tempfile.SpooledTemporaryFile .__ doc__ diz: Wrapper de arquivo temporário, especializado para alternar do StringIO para um arquivo real quando excede um determinado tamanho ou quando um fileno é necessário.
Doug F
5

Cuidado que Popen.communicate(input=s)pode causar problemas se sfor muito grande, porque aparentemente o processo pai o armazenará em buffer antes de bifurcar o subprocesso filho, o que significa que ele precisa "duas vezes mais" de memória usada naquele momento (pelo menos de acordo com a explicação "por baixo do capô" e documentação vinculada encontrada aqui ). No meu caso em particular, shavia um gerador que foi expandido completamente e somente depois gravado para stdinque o processo pai fosse imenso logo antes do nascimento da criança e não restasse memória para bifurcar:

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

Lord Henry Wotton
fonte
5
"""
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()
Lucien Hercaud
fonte
4
Como shell=Trueé tão comumente usado sem um bom motivo, e esta é uma pergunta popular, deixe-me salientar que há muitas situações em que Popen(['cmd', 'with', 'args'])é decididamente melhor do Popen('cmd with args', shell=True)que o shell dividir o comando e os argumentos em tokens, mas não fornecendo nada útil, ao adicionar uma quantidade significativa de complexidade e, assim, também atacar a superfície.
tripleee
2
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)
Dusan
fonte
1

No Python 3.7+, faça o seguinte:

my_data = "whatever you want\nshould match this f"
subprocess.run(["grep", "f"], text=True, input=my_data)

e você provavelmente desejará adicionar capture_output=Truepara obter a saída da execução do comando como uma string.

Nas versões mais antigas do Python, substitua text=Truepor universal_newlines=True:

subprocess.run(["grep", "f"], universal_newlines=True, input=my_data)
Boris
fonte