从python子进程获取输出并给出命令

时间:2011-11-27 10:49:27

标签: python shell command-line subprocess asynchronous

我正在尝试从子进程获取输出,然后根据前面的输出向该进程发出命令。当程序需要进一步输入时,我需要多次执行此操作。 (如果可能的话,我还需要能够隐藏子进程命令提示符)。

我认为这将是一项简单的任务,因为我已经看到这个问题在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

从命令提示符运行时程序似乎关闭。任何想法?

1 个答案:

答案 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)