2011-11-27 67 views
4

我想从子进程获取输出,然后根据前面的输出为该进程提供命令。当程序需要进一步输入时,我需要做这个可变的次数。 (如果可能,我还需要能够隐藏子进程命令提示符)。从python子进程获取输出并给出命令

我觉得这将是一件容易的事,因为我已经看到这个问题在2003年的帖子中讨论过,它接近2012年,它似乎是一个相当普遍的需求,真的好像它应该是一个基本的部分任何编程语言。显然,我错了,不知何故,差不多9年后,仍然没有以稳定,非破坏性,独立于平台的方式完成这项任务的标准方法!

我对文件I/O和缓冲或线程并不十分了解,所以我宁愿选择一个尽可能简单的解决方案。如果有一个模块实现了与python 3.x兼容的模块,我将非常乐意下载它。我意识到有多个问题基本上提出了同样的问题,但我还没有找到解决我试图完成的简单任务的答案。

这是我迄今为止基于各种来源的代码;但是我绝对不知道下一步该怎么做。我所有的尝试都以失败告终,有些设法使用了我CPU的100%(基本上什么都不做),并且不会退出。

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

如果我的问题是不清楚我张贴的样本批处理文件中,我展示了一种情况,即有必要多个命令发送给子进程(如果你输入了不正确的命令字符串程序文字循环)。

@echo off 
:looper 
set INPUT= 
set /P INPUT=Type the correct command string: 
if "%INPUT%" == "command string" (echo you are correct) else (goto looper) 

如果有人可以帮助我,我会非常感激,我相信很多人也会这样!这里

编辑是使用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 

看来,程序在从命令提示符运行时关闭。有任何想法吗?

回答

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) 
+0

非常感谢你为这个它完全处于空闲,但是当我运行修改后的代码(见我在原来的职位编辑)它给了我下面的错误 C:\ Users \用户名>蟒蛇Ç :\ postgis_testing \ shellcomm7.py 输入:某物 回溯(最近通话最后一个): 文件 “C:\ postgis_testing \ shellcomm7.py” 51行,在 p.stdin.write(CMD) IO错误: [Errno 22]无效的参数 从命令提示符运行时,程序似乎关闭。有任何想法吗? – THX1138

+0

@ THX1138:我修改了循环以查询进程并在请求输入之前检查stdin。如果流程退出,它会中断。我也从'read_stdout'参数中删除了过程对象;它在我的初稿中错误地留下了。 – eryksun

+0

当我从命令提示符运行新代码时,出现以下错误: C:\ Users \ username> python C:\ postgis_testing \ shellcomm8.py Traceback(最近调用最后一次): 文件“C:\ postgis_testing \ shellcomm8.py “第38行,在 BUFSIZE = 0) 文件 ”C:\ Python32 \ lib中\ subprocess.py“,线路741,在__init__ restore_signals,start_new_session) 文件” C:\ Python32 \ lib中\ subprocess.py“,第960行,在_execute_child startupinfo) WindowsError:[错误2]系统找不到指定的文件 – THX1138

相关问题