程式扎記: [ Python 常見問題 ] timeout on subprocess readline in python

標籤

2016年11月28日 星期一

[ Python 常見問題 ] timeout on subprocess readline in python

Source From Here 
Question 
I have a small issue that I'm not quite sure how to solve. Here is a minimal example: 
What I Have 
  1. scan_process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)  
  2. while(some_criterium):  
  3.     line = scan_process.stdout.readline()  
  4.     some_criterium = do_something(line)  
What I would like 
  1. scan_process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)  
  2. while(some_criterium):  
  3.     line = scan_process.stdout.readline()  
  4.     if nothing_happens_after_10s:  
  5.         break  
  6.     else:  
  7.         some_criterium = do_something(line)  
I read a line from a subprocess and do something with it. What I want is to exit if no line arrived after a fixed time interval. Any recommendations? 

How-To 
One solution is to write a thread (Here is class StdThd) to constantly read from stdout and read line from that thread: 
  1. import threading, time  
  2.   
  3. class StdThd(threading.Thread):  
  4.     def __init__(self, stdout, threadName='test'):  
  5.         super(StdThd, self).__init__(name = threadName)  
  6.         self.isStop = False  
  7.         self.stdout = stdout        # stdout file object  
  8.         self.lines = []             # list to hold lines  
  9.         self.li = 0                 # Line index  
  10.         self.readBuf = []           # Buffer in reading stdout  
  11.         self.autoStdout = False     # Print line to stdout  
  12.   
  13.     def run(self):  
  14.         while not self.isStop:  
  15.             c = self.stdout.read(1)  
  16.             if c == '':  
  17.                 self.isStop = True  
  18.                 break  
  19.             self.readBuf.append(c)  
  20.             if self.readBuf[-1] == '\n':  
  21.                 self.lines.append(''.join(self.readBuf))  
  22.                 self.readBuf = []  
  23.                 if self.autoStdout:  
  24.                     print self.lines[-1].rstrip('\n')  
  25.   
  26.   
  27.     def readline(self, timeout=5):  
  28.         r'''  
  29.            Read one line from stdout  
  30.   
  31.         @param timeout(int):  
  32.             Timeout in second while reading one line from stdout.  
  33.         '''  
  34.         if self.li < len(self.lines):  
  35.             line = self.lines[self.li]  
  36.             self.li = self.li + 1  
  37.             return line  
  38.         else:  
  39.             loop = timeout * 1000 / 100; cnt = 0  
  40.             while True:  
  41.                 time.sleep(0.1)  
  42.                 if self.li < len(self.lines):  
  43.                     line = self.lines[self.li]  
  44.                     self.li = self.li + 1  
  45.                     return line  
  46.                 cnt = cnt + 1  
  47.                 if cnt > loop:  
  48.                     return None  
Then consider a test shell: 
- test.sh 
  1. #!/bin/sh  
  2. var=0  
  3. while [ "$var" -lt 10 ]; do  
  4.     var=$((var+1))  
  5.     echo "This is for testing: $var"  
  6.     sleep 10  
  7. done  
Then you can use class StdThd to read line from stdout this way: 
>>> import subprocess
>>> p = subprocess.Popen(['./test.sh'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) # Execute test.sh
>>> stdThd = StdThd(p.stdout)
>>> stdThd.start()
>>> stdThd.readline()
'This is for testing: 1\n'
>>> stdThd.readline() # If readline() timeout, None is returned. Default timeout is 5 second
>>> stdThd.is_alive()
True # Thread is running
>>> p.terminate() # Terminate the process
>>> stdThd.is_alive()
False # Thread is not running


Supplement 
How can I run an external command asynchronously from Python?

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!