我有一个Python脚本,可与控制台应用程序(我公司专有)对接。对于该控制台中的命令,我必须通过Popen打开它,然后通过stdin.write将其传递给命令。我实现了一个非阻塞流读取器,以将控制台标准输出实时流式传输到python控制台。这一切都很好。
问题在于控制台永远不会退出并且不会发送返回代码(即poll()始终返回None,wait()永远挂起)。 因此,我必须处理输出以确定命令何时完成。另一个警告是,我通过stdin.write()发送的命令实际上执行了一个脚本,该脚本又在控制台中执行了一系列命令。控制台将在执行命令时暂停,并且stdout.readline()将立即返回''。所以我不能用它来打破循环。我拥有的代码可以工作99%,但是我无法找到一种方法来优雅地退出,关闭子进程并在NonBlockingStreamReader中终止线程。所以这是代码:
# nbstreamready.py - source: http://eyalarubas.com/python-subproc-nonblock.html
from threading import Thread
from queue import Queue, Empty
class NonBlockingStreamReader:
def __init__(self, stream):
'''
stream: the stream to read from.
Usually a process' stdout or stderr.
'''
self._s = stream
self._q = Queue()
def _populateQueue(s, q):
'''
Collect lines from 'stream' and put them in 'quque'.
'''
while True:
line = s.readline()
if line:
q.put(line)
else:
raise UnexpectedEndOfStream
self._t = Thread(target=_populateQueue, args=(self._s, self._q), name='Thread-nsbr')
self._t.daemon = True
self._t.start() # start collecting lines from the stream
def readline(self, timeout=None):
try:
return self._q.get(block=timeout is not None, timeout=timeout)
except Empty:
return None
class UnexpectedEndOfStream(Exception):
pass
主类:
# etconsole.py
from subprocess import Popen, PIPE, STDOUT
from time import sleep
from nbstreamreader import NonBlockingStreamReader
from paths import scriptFolder
class ETConsole:
def __init__(self, scriptFileName, etInstallFolder='C:\\Program Files (x86)\\Engineering Tool 2010\\'):
self.exeFilePath = etInstallFolder + 'ConsoleEngineeringTool.exe'
self.scriptFile = scriptFolder + scriptFileName
self.process = Popen(self.exeFilePath, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
self.nbsr = NonBlockingStreamReader(self.process.stdout)
self.nbsr.start()
def getoutput(self):
while True:
if self.process.returncode is not None:
print(f'ET Returncode: {self.process.returncode}')
if self.process.poll() is not None:
print(f'Poll: {self.process.returncode}')
output = self.nbsr.readline()
if output is not None:
outchar = output.decode().rstrip()
if 'Result executescript' in outchar:
print(outchar)
break
print(outchar)
def runscript(self):
sleep(.5)
cmd = 'executescript /import ' + self.scriptFile + '\n'
cmdbytes = bytes(cmd, 'utf-8')
# print("Command = " + cmd)
self.process.stdin.write(cmdbytes)
self.process.stdin.flush()
self.getoutput()
def terminate(self):
self.process.terminate()
因此,当我们在控制台中执行时,getoutput()方法正确地中断了循环读取标准输出。为了清楚起见,我引用了控制台exe中的流式标准输出并将其缩进。
>>>from etconsole import ETConsole
et = ETConsole('RIGM-100012_testscript.txt')
et.runscript()
"ConsoleET>
### 2018-11-07 12:52:55 Command executescript
### 2018-11-07 12:52:55 Command setarchitecture
### 2018-11-07 12:52:55 Progress setarchitecture "" 0...10...20...30...40...50...60...70...80...90...100
### 2018-11-07 12:52:55 OCULAR-START setarchitecture
ApplicationMode = Tea2
### 2018-11-07 12:52:55 OCULAR-END setarchitecture
### 2018-11-07 12:52:55 Result setarchitecture 0
### 2018-11-07 12:52:55 Command login
### 2018-11-07 12:52:55 Progress login "" 0...10...20...30...40...50...60...70...80...90...100
### 2018-11-07 12:53:01 OCULAR-START login
LoginToSews = 0
### 2018-11-07 12:53:01 OCULAR-END login
### 2018-11-07 12:53:01 Result login 0
### 2018-11-07 12:53:02 Command connect
### 2018-11-07 12:53:02 Progress connect "" 0...10...20...30...40...50...60...70...80...90...100
### 2018-11-07 12:53:02 OCULAR-START connect
Connection-Status = 0
### 2018-11-07 12:53:02 OCULAR-END connect
### 2018-11-07 12:53:02 Result connect 0
### 2018-11-07 12:53:02 Command disconnect
### 2018-11-07 12:53:02 Progress disconnect "" 0...10...20...30...40...50...60...70...80...90...100
### 2018-11-07 12:53:02 OCULAR-START disconnect
Disconnect-Status = 0
### 2018-11-07 12:53:02 OCULAR-END disconnect
### 2018-11-07 12:53:02 Result disconnect 0
### 2018-11-07 12:53:02 Result executescript 0"
>>>
这正是我想要的,但这使子进程(在这种情况下为ConsoleEngineeringTool)保持运行状态(因为我使用stdin.write而不是communication()等),所以让我们手动终止该过程:
>>>et.terminate()
Exception in thread Thread-nsbr:
Traceback (most recent call last):
File "C:\Program Files\Python\Python36\lib\threading.py", line 916, in _bootstrap_inner
self.run()
File "C:\Program Files\Python\Python36\lib\threading.py", line 864, in run
self._target(*self._args, **self._kwargs)
File "C:\Users\ru3944v\PycharmProjects\DP3_TestHelper\nbstreamreader_original.py", line 25, in _populateQueue
raise UnexpectedEndOfStream
nbstreamreader_original.UnexpectedEndOfStream
我尝试使用asyncio创建流读取器link,但是也不起作用,它也在等待poll()返回某些内容。我也尝试过使nbstreamreader中的线程成为可停止的线程,并在主类中调用其stop事件,这也不起作用。
因此,我需要一种优雅/安全的方法来帮助您1.关闭子进程exe和2.关闭nonblockingstreamreader中的线程。
非常感谢。
P.S。我正在使用Windows 7,Python 3.6。是的,我已经做了很多寻找答案的尝试,并尝试了许多不同的方法。