- http://docs.python.org/library/os.html#os.system
- http://docs.python.org/library/os.html#os.popen
- http://maestric.com/doc/unix/ubuntu_sudo_without_password
用Python监视进程是否结束
平时不打游戏在Linux下的时间比较长,用VirtualBox装了个XP可以上上QQ,用用其他一些只有Windows版的软件,这两天下BoA的演唱会又用到了网盘,而且网盘的客户端软件只支持Windows,这样就只能在VirtualBox里下载,可是下载结束自动关机的功能就失效了。。于是想到能不能VirtualBox自动关闭的同时,也能让Linux也一起关闭。
不知道有什么简便的方法可以实现,只想到了用Python写个小脚本(过去一直用Python来解决一些小问题),于是去网上搜了Python相关的类库,其中os.system
函数可以调用Linux命令,初步想了下,准备用一个线程来不断执行ps
命令,并检查VirtualBox的进程是否还存在,如果不存在了,就执行halt
。
不过尝试了一下用os.system
调用ps
后,输出的结果无法保存到变量中,看了一个帖子,发现os.popen
可以解决,os.popen
可以像打开文件一样执行一条Linux命令,命令返回的结果可以通过读取文件的方式获得。
通过ps ax|grep VirtualBox|grep xp
的组合命令可以得到正在执行xp的虚拟机进程,其中xp是自己设定的虚拟机的标题,一般得到的结果会有2条,1条就是虚拟机进程,另1条是grep命令本身,所以当得到的结果只剩下grep命令本身时,说明虚拟机已经关闭。
由于我用的Linux发行版是Ubuntu,执行关机命令需要sudo并且输入密码,所以要让自动关机能够实现还有一步是,设定用户执行sudo不需要输入密码,这可以通过sudo visudo
对sudoers
文件进行修改,在文件最后加上chocobo ALL=(ALL) NOPASSWD: ALL
其中chocobo
是用户名。
好了,准备工作都做完,就可以执行脚本监视进程啦,具体代码如下:
#!/usr/bin/env python import os import time def autohalt(): while True: ps_string = os.popen('ps ax | grep VirtualBox | grep xp','r').read() ps_strings = ps_string.strip().split('/n') if len(ps_strings) < 2: os.system('sudo halt') return else: print 'Still',len(ps_strings),'Processes, waiting 10s...' time.sleep(10) if __name__=='__main__': autohalt()
参考资料:
春节前的一段时间,重新拾起近一年没动过的ForeverFantasy。
虽然一年内没什么更新,但是我却一直都在用,基本上只限于将用Vim写好的Markdown格式的文档转换成HTML。
重写了相当一部分代码,较大程度地改变了界面布局,突然发现经过一年的沉淀,对wxPython的理解增进了不少,开发起来比起去年这个时候清楚了很多。
这些天来一直坚持着每天或多或少的做一些,如果说有什么主要的进展的话,那就是界面的重构,以及昨天实现了调用Vim编辑文档并回收文档内容的功能。
ForeverFantasy和Vim协同的一个最大的问题就是如何判断Vim已经退出。Python调用外部程序的方法有很多,比如传统的commands模块、os.system()等,subprocess是致力于替代这些旧有的方式的一个模块,它的一个特点是可以在启动一个外部程序作为子进程后还能监控这个进程的运行状态。这为ForeverFantasy在Vim退出后回收文档内容提供了更简捷的途径。
下面的代码可以说明如何使用subprocess运行外部程序并监控运行状态:
-
import subprocess
-
process = subprocess. Popen ( 'gvim', shell= True )
-
status = process. poll ( )
-
if 0 == status:
-
print 'The external program exited.'
-
if status is None:
-
print 'The external program is still running.'
理论上,可以拿实例process的poll()方法监视进程的运行状态,而且这一点在Windows上也确实可以做到,但是到了Linux下,诡异的事出现了,即使刚刚打开gvim,poll()方法也会马上返回一个0,同时process.pid的值总是和实际在运行的那个gvim进程的pid的值差3,而且在虚拟终端中输入gvim命令也不会阻塞终端,就像别的命令加上“&”符号的效果一样。尝试用strace跟踪gvim的运行,试图找到问题原因,无果。我猜想可能/usr/bin/gvim是一个跳板,它启动后会启动一个新的gvim进程。总之,这个方法在Linux下是行不通的。
因此我觉得只能另寻出路了,既然不能监控gvim进程,那就监控gvim进程所编辑的文件,只要这个文件不被任何进程占用,就可以判定gvim已退出。这一点,在Linux下易如反掌,不用说,lsof当仁不让。
用commands.getstatusoutput('lsof file.txt')测试发现,如果文件file.txt被某进程占用,则返回的状态值为0,反之,返回256。
最终,我在程序中使用了两种判断方式,在Windows平台使用subprocess跟踪gvim.exe进程,而在Linux及Unix平台使用lsof检查文档占用情况。
剩下的就是进行这个判断的时间问题了。
很显然,如果在子进程被启动后马上使用while循环不停的检查,一来必须使用多线程,二来系统资源占用也会很高。这时就需要利用wxPython的事件机制了,当ForeverFantasy启动Gvim时,主窗口失去焦点,而当Gvim退出时,ForeverFantasy又会获得焦点,只要能在ForeverFantasy窗口获得焦点时做一次检查即可。不过,在选择最合适的事件的问题上,又是一波三折。
在wxPython的API文档中没有找到事件列表,倒是在Wiki中找到了。顾名思义,觉得wx.EVT_SET_FOCUS比较靠谱,但试用失败,看API中关于FocusEvent的说明,这个事件适用于窗口控件;然后又试了wx.EVT_CHILD_FOCUS,只有在窗口包含的控件中有获得焦点的情况才会触发;最后才发现wx.EVT_ACTIVATE,这个事件会在窗口失去焦点和获得焦点时各触发一次,使用GetActive()方法可以判断是获得焦点还是失去焦点。
完成与Vim的协同使ForeverFantasy在我手上由原来单纯的格式转换工具进化为基本可用的文档编辑器,就算是Milestone 2吧。
此外,还有一些小的经验:
1. 调用非环境变量下的程序,即命令中必须带程序所在的路径时,应当将程序所在目录的完整路径以自然字符串的形式传递给subprocess.Popen类的构造方法的cwd参数,即如下所示:
-
process = subprocess. Popen ( 'gvim.exe', cwd=r 'C:/program files/vim/vim72', shell= True )
这样可以有效避免路径中的空格和特殊字符对命令执行的影响。
2. Vim编辑一个文档时,实际操作的是一个临时文件,而不是原文件,这个临时文件与原文件同路径,名称为在原文件名的基础上,前面加一个句点,后面加后缀“.swp”。应该用lsof监控这个临时文件,才可以判断出编辑该文档的Vim进程的运行状态。由于对于不存在的文件使用lsof命令的返回值也是256,故可以同时判断临时文件和原文件的占用情况,这样就为对其它编辑器的支持奠定了基础。
2010-03-10 Wednesday 22:52:13 更新
感谢KL童鞋和依云童鞋指教,果然加上-f参数就可以了。