我正在尝试从子进程获取输出,然后根据前面的输出向该进程发出命令。当程序需要进一步输入时,我需要多次执行此操作。 (如果可能的话,我还需要能够隐藏子进程命令提示符)。
我认为这将是一项简单的任务,因为我已经看到这个问题在2003年的帖子中讨论过,而且差不多是2012年,它似乎是一个非常普遍的需求,看起来它应该是任何基本的一部分。编程语言。显然我错了,差不多9年之后,仍然没有以稳定,非破坏性,独立于平台的方式完成这项任务的标准方法!
我对文件i / o和缓冲或线程并不是很了解,所以我宁愿选择尽可能简单的解决方案。如果有一个模块完成了与python 3.x兼容的模块,我将非常愿意下载它。我意识到有多个问题基本上都是一样的问题,但我还没有找到解决我想要完成的简单任务的答案。
这是我到目前为止基于各种来源的代码;但我完全不知道接下来该做什么。我的所有尝试都以失败告终,有些人设法使用100%的CPU(基本上什么也没做)并且不会退出。
import subprocess
from subprocess import Popen, PIPE
p = Popen(r'C:\postgis_testing\shellcomm.bat',stdin=PIPE,stdout=PIPE,stderr=subprocess.STDOUT shell=True)
stdout,stdin = p.communicate(b'command string')
如果我的问题不清楚,我发布了示例批处理文件的文本,我演示了一种情况,其中需要向子进程发送多个命令(如果键入错误的命令字符串,程序循环)。 / p>
@echo off
:looper
set INPUT=
set /P INPUT=Type the correct command string:
if "%INPUT%" == "command string" (echo you are correct) else (goto looper)
如果有人能帮助我,我会非常感激,并且我相信很多其他人也会这样做!
EDIT这里是使用eryksun代码的功能代码(下一篇文章):
import subprocess
import threading
import time
import sys
try:
import queue
except ImportError:
import Queue as queue
def read_stdout(stdout, q, p):
it = iter(lambda: stdout.read(1), b'')
for c in it:
q.put(c)
if stdout.closed:
break
_encoding = getattr(sys.stdout, 'encoding', 'latin-1')
def get_stdout(q, encoding=_encoding):
out = []
while 1:
try:
out.append(q.get(timeout=0.2))
except queue.Empty:
break
return b''.join(out).rstrip().decode(encoding)
def printout(q):
outdata = get_stdout(q)
if outdata:
print('Output: %s' % outdata)
if __name__ == '__main__':
#setup
p = subprocess.Popen(['shellcomm.bat'], stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
bufsize=0, shell=True) # I put shell=True to hide prompt
q = queue.Queue()
encoding = getattr(sys.stdin, 'encoding', 'utf-8')
#for reading stdout
t = threading.Thread(target=read_stdout, args=(p.stdout, q, p))
t.daemon = True
t.start()
#command loop
while p.poll() is None:
printout(q)
cmd = input('Input: ')
cmd = (cmd + '\n').encode(encoding)
p.stdin.write(cmd)
time.sleep(0.1) # I added this to give some time to check for closure (otherwise it doesn't work)
#tear down
for n in range(4):
rc = p.poll()
if rc is not None:
break
time.sleep(0.25)
else:
p.terminate()
rc = p.poll()
if rc is None:
rc = 1
printout(q)
print('Return Code: %d' % rc)
但是,当从命令提示符运行脚本时,会发生以下情况:
C:\Users\username>python C:\postgis_testing\shellcomm7.py
Input: sth
Traceback (most recent call last):
File "C:\postgis_testing\shellcomm7.py", line 51, in <module>
p.stdin.write(cmd)
IOError: [Errno 22] Invalid argument
从命令提示符运行时程序似乎关闭。任何想法?
答案 0 :(得分:3)
此演示使用专用线程从stdout读取。如果您四处搜索,我相信您可以在面向对象的界面中找到更完整的实现。至少我可以说在Python 2.7.2和3.2.2中提供的批处理文件对我有用。
shellcomm.bat:
@echo off
echo Command Loop Test
echo.
:looper
set INPUT=
set /P INPUT=Type the correct command string:
if "%INPUT%" == "command string" (echo you are correct) else (goto looper)
这是基于命令序列“错误”,“仍然错误”和“命令字符串”得到的输出:
Output:
Command Loop Test
Type the correct command string:
Input: wrong
Output:
Type the correct command string:
Input: still wrong
Output:
Type the correct command string:
Input: command string
Output:
you are correct
Return Code: 0
对于读取管道输出,readline
有时可能有效,但批处理文件中的set /P INPUT
自然不会写一行结尾。所以我使用lambda: stdout.read(1)
一次读取一个字节(效率不高,但它有效)。读取功能将数据放在队列中。主线程在写入命令后从队列中获取输出。在此get
调用上使用超时会使其等待一小段时间以确保程序正在等待输入。相反,您可以检查输出以获取提示,以了解程序何时输入。
所有这一切,你不能指望像这样的设置普遍工作,因为你试图与之交互的控制台程序可能会在管道传输时缓冲其输出。在Unix系统中,有一些可用的实用程序命令可以插入管道以将缓冲修改为非缓冲,行缓冲或给定大小 - 例如stdbuf。还有一些方法可以让程序认为它与pty连接(参见pexpect)。但是,如果您无法访问程序的源代码以使用setvbuf显式设置缓冲,我在Windows上无法解决此问题。
import subprocess
import threading
import time
import sys
if sys.version_info.major >= 3:
import queue
else:
import Queue as queue
input = raw_input
def read_stdout(stdout, q):
it = iter(lambda: stdout.read(1), b'')
for c in it:
q.put(c)
if stdout.closed:
break
_encoding = getattr(sys.stdout, 'encoding', 'latin-1')
def get_stdout(q, encoding=_encoding):
out = []
while 1:
try:
out.append(q.get(timeout=0.2))
except queue.Empty:
break
return b''.join(out).rstrip().decode(encoding)
def printout(q):
outdata = get_stdout(q)
if outdata:
print('Output:\n%s' % outdata)
if __name__ == '__main__':
ARGS = ["shellcomm.bat"] ### Modify this
#setup
p = subprocess.Popen(ARGS, bufsize=0, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
q = queue.Queue()
encoding = getattr(sys.stdin, 'encoding', 'utf-8')
#for reading stdout
t = threading.Thread(target=read_stdout, args=(p.stdout, q))
t.daemon = True
t.start()
#command loop
while 1:
printout(q)
if p.poll() is not None or p.stdin.closed:
break
cmd = input('Input: ')
cmd = (cmd + '\n').encode(encoding)
p.stdin.write(cmd)
#tear down
for n in range(4):
rc = p.poll()
if rc is not None:
break
time.sleep(0.25)
else:
p.terminate()
rc = p.poll()
if rc is None:
rc = 1
printout(q)
print('\nReturn Code: %d' % rc)