set timeout for a shell command in python
I wanted to run a shell command in python without knowing if the shell command is going to exit within reasonable time (adplay that was, sometimes it simply hangs).
Update: the "task" module of Rob Hooft seems to solve this exact problem. At the time I wrote this, the python.net website was down. I leave my solution here just for archive purpose.
-
def timeout_command(command, timeout):
-
"""call shell-command and either return its output or kill it
-
if it doesn't normally exit within timeout seconds and return None"""
-
import subprocess, datetime, os, time, signal
-
start = datetime.datetime.now()
-
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-
while process.poll() is None:
-
time.sleep(0.1)
-
now = datetime.datetime.now()
-
if (now - start).seconds> timeout:
-
os.kill(process.pid, signal.SIGKILL)
-
os.waitpid(-1, os.WNOHANG)
-
return None
-
return process.stdout.read()
-
>>> output = timeout_command(["sleep", "10"], 2)
-
None
-
>>> output = timeout_command(["sleep", "1"], 2)
The process can be killed when it has run for too long (the os.waitpid waits for the kill to end and avoids defunct-processes) and furthermore the Popen'ed process' printed is caught and returned if it doesn't timeout. However, subprocess.Popen is called with a list as argument. That means, that the command isn't passed to a shell and furthermore you can just call one command with options, nothing more.
I tried to use the function but I found two problems.
First, the command argument is passed as a string, not a list. Using a string I was having the following error:
OSError: [Errno 2] No such file or directory
Then, a 'No such process' error appeared when the process has run for too long. That happend when the os.kill function was called for the second time. I added a 'return None' to fix this problem.
Thanks Juan, I fixed the function and the example so the errors won't occur anymore. Sorry for the inconvenience.
Note that os.kil is not available on windows. Too bad
With a one more line of code, and a change to another, your function will act exactly like popen: Note, this requires python 2.4+ Enjoy!
For Windows, you can use:
import ctypes
TerminateProcess = ctypes.windll.kernel32.TerminateProcess
TerminateProcess(int(process.handle), -1)
Small typo in the above Windows code. Should read:
import ctypes
TerminateProcess = ctypes.windll.kernel32.TerminateProcess
TerminateProcess(int(popen._handle), -1)
(Note the underscore added before "handle").
Also note that the process module has timeout built in. No need to poll, no need to terminate the spawned process:
http://trentm.com/projects/process/
import process
try:
p = process.ProcessOpen("notepad.exe")
p.wait(timeout=5)
except process.ProcessError:
print "timeout!"