杀死子进程后Shell挂起

时间:2014-04-06 20:25:38

标签: python subprocess

我知道有很多类似的问题,例如this onethis one,可能还有一些,但它们似乎都不适用于我的特殊情况。我对subprocess.Popen()的工作方式缺乏了解也无济于事。

我想要实现的是:启动一个子进程(命令行无线电播放器),它也将数据输出到终端,也可以接收输入 - 等待一段时间 - 终止子进程 - 退出shell。我在OSX 10.9上运行python 2.7

案例1。 这将启动无线电播放器(但仅限音频!),终止该过程,退出。

import subprocess
import time

p = subprocess.Popen(['/bin/bash', '-c', 'mplayer http://173.239.76.147:8090'],
                     stdin=subprocess.PIPE, stdout=subprocess.PIPE, shell=False,
                     stderr=subprocess.STDOUT)
time.sleep(5)
p.kill()

案例2。 这将启动无线电播放器,输出无线电名称,歌曲,比特率等信息,并接受输入。它终止子进程,但它永远不存在shell,即使在使用' Ctrl-C'终端也无法使用。

p = subprocess.Popen(['/bin/bash', '-c', 'mplayer http://173.239.76.147:8090'],
                     shell=False)
time.sleep(5)
p.kill()

关于如何做的任何想法?如果没有其他选择的话,我甚至在考虑为子流程打开 slave-shell 的可能性(当然它也是我不知道的东西)。谢谢!

2 个答案:

答案 0 :(得分:3)

似乎mplayer使用curses库,kill()terminate()时,由于某种原因,它不会清理库正确地陈述。

要恢复终端状态,您可以使用reset命令。

演示:

import subprocess, time

p = subprocess.Popen(['mplayer', 'http://173.239.76.147:8090'])
time.sleep(5)
p.terminate()
p.wait()  # important!

subprocess.Popen(['reset']).wait()

print('Hello, World!')

原则上应该可以使用stty sane,但它对我来说效果不佳。


正如塞巴斯蒂安指出的那样,上面的代码(现已添加)中缺少wait()次调用。通过此wait()使用terminate()调用,终端不会搞砸(因此不需要reset )。

没有wait()我有时会遇到python进程和mplayer之间混合输出的问题。

另外,正如Sebastian所指出的,mplayer特有的解决方案是将q发送到mplayer的标准输出以退出它。

我保留使用reset的代码,因为它适用于使用curses库的任何程序,无论它是否正确地删除了库,因此它在其他干净退出不可能的情况下可能会有用。

答案 1 :(得分:2)

  

我想要实现的是:启动一个子进程(命令行无线电播放器),它也将数据输出到终端,也可以接收输入 - 等待一段时间 - 终止子进程 - 退出shell。我在OSX 10.9上运行python 2.7

在我的系统上,mplayer接受键盘命令,例如,q停止播放并退出:

#!/usr/bin/env python
import shlex
import time
from subprocess import Popen, PIPE

cmd = shlex.split("mplayer http://www.swissradio.ch/streams/6034.m3u")
p = Popen(cmd, stdin=PIPE)
time.sleep(5)
p.communicate(b'q')

它开始mplayer调整为公共领域经典;等了5秒;要求mplayer退出并等待它退出。输出将转到终端(与python脚本输出相同的位置)。

我还尝试了p.kill()p.terminate()p.send_signal(signal.SIGINT) Ctrl + C )。 p.kill()创建了该进程挂起的展示。可能的解释:p.kill()会打开一些管道,例如,如果stdout=PIPE那么您的Python脚本可能会挂起p.stdout.read(),即它会杀死父mplayer进程,但可能会有一个孩子保持管道打开的过程。 p.terminate()p.send_signal(signal.SIGINT) - mplayer以有序的方式退出。我尝试过的所有变种都不需要reset


  

我应该如何同时使用Python和键盘输入?我是否需要两个不同的子流程以及如何将键盘输入重定向到PIPE?

放弃stdin=PIPE并拨打p.terminate(); p.wait()而不是p.communicate(b'q')会更简单。

如果你想保留stdin=PIPE,那么一般原则是:从sys.stdin读取,写入p.stdin直到超时发生。鉴于mplayer需要一个字母命令,您需要能够read one character at at time from sys.stdin。写入部分很简单:p.stdin.write(c)(设置bufsize=0以避免在Python端缓冲。mplayer不会缓冲其标准输入,因此您不必担心它)。

您不需要两个不同的子流程。要实施timeout,您可以在threading.Timer(5, p.stdin.write, [b'q']).start()上使用select.selectsys.stdin超时。

  

我想使用旧的raw_input的东西与它无关,或者?

raw_input()不适合mplayer,因为它会读取整行,但mplayer一次需要一个字符。