Python的 – 如何将string传递到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。 我如何解决这个问题?

Popen.communicate()文档:

请注意,如果要将数据发送到进程的stdin,则需要使用stdin = PIPE创buildPopen对象。 同样,要在结果元组中获得除None以外的任何内容,您还需要输出stdout = PIPE和/或stderr = PIPE。

replaceos.popen *

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

警告使用通信()而不是stdin.write(),stdout.read()或stderr.read()来避免由于任何其他OSpipe道缓冲区填满和阻塞subprocess而导致的死锁。

所以你的例子可以写成如下:

 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将input作为string传递给外部命令并获取其退出状态,并将其输出作为string返回到一次调用中:

 #!/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('one\ntwo\nthree\nfour\nfive\nsix\n') >>> p.communicate()[0] 'four\nfive\n' >>> p.stdin.close() 

有更好的吗?

我有点惊讶没有人build议创build一个pipe道,这是我认为最简单的方法来传递一个string到一个subprocess的标准input:

 read, write = os.pipe() os.write(write, "stdin input here") os.close(write) subprocess.check_call(['your-command'], stdin=read) 

我使用python3,发现你需要先编码你的string,然后才能把它传递给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对象不足以接近文件鸭子以适应subprocess.Popen”

🙂

恐怕不是。 pipe道是一个低级别的操作系统概念,所以它绝对需要一个由操作系统级文件描述符表示的文件对象。 您的解决方法是正确的。

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

如果您使用的是Python 3.4或更高版本,那么有一个漂亮的解决scheme。 使用input参数而不是stdin参数,它接受一个字节参数:

 output = subprocess.check_output( ["sed", "s/foo/bar/"], input=b"foo", ) 
 """ 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() 

请注意,如果s太大, Popen.communicate(input=s)可能会给你带来麻烦,因为显然父进程会分叉subprocess之前缓冲它,这意味着在那一点上它需要“两倍于”内存根据“引擎盖下”的解释和链接文件在这里find)。 在我的具体情况中, s是一个第一次完全展开的生成器,然后写到stdin所以父进程在subprocess之前是巨大的,没有留下任何内存来分叉:

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)