在 Windows 上将 ^C 发送到 Python 子进程对象 [英] Sending ^C to Python subprocess objects on Windows
问题描述
我有一个测试工具(用 Python 编写),需要通过发送 ^C 来关闭被测程序(用 C 编写).在 Unix 上,
proc.send_signal(signal.SIGINT)
完美运行.在 Windows 上,这会引发错误(不支持信号 2"或类似内容).我在 Windows 上使用 Python 2.7,所以我觉得我应该可以这样做
proc.send_signal(signal.CTRL_C_EVENT)
但这根本没有任何作用.我需要做什么?这是创建子进程的代码:
# Windows 需要一个额外的参数传递给 subprocess.Popen,# 但是这个常量在 Unix 上没有定义.尝试:kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP除了 AttributeError: passproc = subprocess.Popen(argv,stdin=open(os.path.devnull, "r"),标准输出=子进程.PIPE,stderr=subprocess.PIPE,**夸格)
有一个解决方案是使用包装器(如 Vinay 提供的链接中所述)在新的控制台窗口中使用 Windows start 命令.
包装器代码:
#wrapper.py导入子进程、时间、信号、系统、操作系统定义信号处理程序(信号,帧):时间.睡眠(1)打印 'Ctrl+C 在 wrapper.py 中收到'信号.信号(信号.SIGINT,信号处理程序)打印wrapper.py 开始"subprocess.Popen("python demo.py")time.sleep(3) #在这里替换为你的 IPC 代码,它等待一个火 CTRL-C 请求os.kill(信号.CTRL_C_EVENT,0)
捕获CTRL-C的程序代码:
#demo.py导入信号、系统、时间定义信号处理程序(信号,帧):打印 'Ctrl+C 在 demo.py 中收到'时间.睡眠(1)sys.exit(0)信号.信号(信号.SIGINT,信号处理程序)打印demo.py 开始"#signal.pause() # 在 Windows 下不起作用而(真):时间.睡眠(1)
启动包装器,例如:
PythonPrompt>导入子流程PythonPrompt>subprocess.Popen("启动python wrapper.py", shell=True)
您需要添加一些 IPC 代码,以允许您控制触发 os.kill(signal.CTRL_C_EVENT, 0) 命令的包装器.为此,我在我的应用程序中使用了套接字.
说明:
前置信息
send_signal(CTRL_C_EVENT)
不起作用,因为CTRL_C_EVENT
仅用于os.kill
.[REF1]os.kill(CTRL_C_EVENT)
向当前 cmd 窗口中运行的所有进程发送信号 [REF2]Popen(..., creationflags=CREATE_NEW_PROCESS_GROUP)
不起作用,因为进程组忽略了CTRL_C_EVENT
.[REF2]这是 python 文档中的一个错误 [REF3]立>
已实施的解决方案
- 使用 Windows shell 命令 start 让您的程序在不同的 cmd 窗口中运行.
- 在您的控制应用程序和应该获得 CTRL-C 信号的应用程序之间添加一个 CTRL-C 请求包装器.包装器将在与应获得 CTRL-C 信号的应用程序相同的 cmd 窗口中运行.
- 包装器将自行关闭,程序将通过向 cmd 窗口中的所有进程发送 CTRL_C_EVENT 来获取 CTRL-C 信号.
- 控制程序应该能够请求包装器发送 CTRL-C 信号.这可以通过 IPC 方式实现,例如套接字.
有用的帖子是:
我必须删除链接前面的 http,因为我是新用户,不允许发布两个以上的链接.
- http://social.msdn.microsoft.com/Forums/en-US/windowsgeneraldevelopmentissues/thread/dc9586ab-1ee8-41aa-a775-cf4828ac1239/#6589714f-12a7-447e-b272f3173/a>
- 可以我向 Windows 上的应用程序发送了 ctrl-C (SIGINT)?
- 将 SIGINT 发送到 python 的子进程
- http://bugs.python.org/issue9524
- http://ss64.com/nt/start.html
- http://objectmix.com/python/387639-sending-cntrl-c.html#post1443948
更新:基于 IPC 的 CTRL-C 包装器
在这里你可以找到一个自写的 python 模块,它提供了一个 CTRL-C 包装,包括一个基于套接字的 IPC.语法与子流程模块非常相似.
用法:
<预><代码>>>>进口控制>>>p1 = winctrlc.Popen("python demo.py")>>>p2 = winctrlc.Popen("python demo.py")>>>p3 = winctrlc.Popen("python demo.py")>>>p2.send_ctrl_c()>>>p1.send_ctrl_c()>>>p3.send_ctrl_c()代码
导入套接字导入子流程导入时间随机导入导入信号,操作系统,系统类Popen:_port = random.randint(10000, 50000)_连接 = ''def _start_ctrl_c_wrapper(self, cmd):cmd_str = "start \"\" python winctrlc.py "+"\""+cmd+"\""+" "+str(self._port)subprocess.Popen(cmd_str, shell=True)def_create_connection(self):self._connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)self._connection.connect(('localhost', self._port))def send_ctrl_c(self):self._connection.send(Wrapper.TERMINATION_REQ)self._connection.close()def __init__(self, cmd):self._start_ctrl_c_wrapper(cmd)self._create_connection()类包装器:TERMINATION_REQ = "用 CTRL-C 终止"def _create_connection(self, port):s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.bind(('localhost', 端口))s.听(1)conn, addr = s.accept()返回连接def _wait_on_ctrl_c_request(self, conn):为真:数据 = conn.recv(1024)如果数据== self.TERMINATION_REQ:ctrl_c_received = 真休息别的:ctrl_c_received = 假返回 ctrl_c_receiveddef _cleanup_and_fire_ctrl_c(self, conn):conn.close()os.kill(信号.CTRL_C_EVENT,0)def _signal_handler(自我,信号,帧):时间.睡眠(1)sys.exit(0)def __init__(self, cmd, port):信号.信号(信号.SIGINT,self._signal_handler)subprocess.Popen(cmd)conn = self._create_connection(port)ctrl_c_req_received = self._wait_on_ctrl_c_request(conn)如果 ctrl_c_req_received:self._cleanup_and_fire_ctrl_c(conn)别的:sys.exit(0)如果 __name__ == "__main__":command_string = sys.argv[1]port_no = int(sys.argv[2])包装器(command_string,port_no)
I have a test harness (written in Python) that needs to shut down the program under test (written in C) by sending it ^C. On Unix,
proc.send_signal(signal.SIGINT)
works perfectly. On Windows, that throws an error ("signal 2 is not supported" or something like that). I am using Python 2.7 for Windows, so I have the impression that I should be able to do instead
proc.send_signal(signal.CTRL_C_EVENT)
but this doesn't do anything at all. What do I have to do? This is the code that creates the subprocess:
# Windows needs an extra argument passed to subprocess.Popen,
# but the constant isn't defined on Unix.
try: kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP
except AttributeError: pass
proc = subprocess.Popen(argv,
stdin=open(os.path.devnull, "r"),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
**kwargs)
There is a solution by using a wrapper (as described in the link Vinay provided) which is started in a new console window with the Windows start command.
Code of the wrapper:
#wrapper.py
import subprocess, time, signal, sys, os
def signal_handler(signal, frame):
time.sleep(1)
print 'Ctrl+C received in wrapper.py'
signal.signal(signal.SIGINT, signal_handler)
print "wrapper.py started"
subprocess.Popen("python demo.py")
time.sleep(3) #Replace with your IPC code here, which waits on a fire CTRL-C request
os.kill(signal.CTRL_C_EVENT, 0)
Code of the program catching CTRL-C:
#demo.py
import signal, sys, time
def signal_handler(signal, frame):
print 'Ctrl+C received in demo.py'
time.sleep(1)
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
print 'demo.py started'
#signal.pause() # does not work under Windows
while(True):
time.sleep(1)
Launch the wrapper like e.g.:
PythonPrompt> import subprocess
PythonPrompt> subprocess.Popen("start python wrapper.py", shell=True)
You need to add some IPC code which allows you to control the wrapper firing the os.kill(signal.CTRL_C_EVENT, 0) command. I used sockets for this purpose in my application.
Explanation:
Preinformation
send_signal(CTRL_C_EVENT)
does not work becauseCTRL_C_EVENT
is only foros.kill
. [REF1]os.kill(CTRL_C_EVENT)
sends the signal to all processes running in the current cmd window [REF2]Popen(..., creationflags=CREATE_NEW_PROCESS_GROUP)
does not work becauseCTRL_C_EVENT
is ignored for process groups. [REF2] This is a bug in the python documentation [REF3]
Implemented solution
- Let your program run in a different cmd window with the Windows shell command start.
- Add a CTRL-C request wrapper between your control application and the application which should get the CTRL-C signal. The wrapper will run in the same cmd window as the application which should get the CTRL-C signal.
- The wrapper will shutdown itself and the program which should get the CTRL-C signal by sending all processes in the cmd window the CTRL_C_EVENT.
- The control program should be able to request the wrapper to send the CTRL-C signal. This might be implemnted trough IPC means, e.g. sockets.
Helpful posts were:
I had to remove the http in front of the links because I'm a new user and are not allowed to post more than two links.
- http://social.msdn.microsoft.com/Forums/en-US/windowsgeneraldevelopmentissues/thread/dc9586ab-1ee8-41aa-a775-cf4828ac1239/#6589714f-12a7-447e-b214-27372f31ca11
- Can I send a ctrl-C (SIGINT) to an application on Windows?
- Sending SIGINT to a subprocess of python
- http://bugs.python.org/issue9524
- http://ss64.com/nt/start.html
- http://objectmix.com/python/387639-sending-cntrl-c.html#post1443948
Update: IPC based CTRL-C Wrapper
Here you can find a selfwritten python module providing a CTRL-C wrapping including a socket based IPC. The syntax is quite similiar to the subprocess module.
Usage:
>>> import winctrlc
>>> p1 = winctrlc.Popen("python demo.py")
>>> p2 = winctrlc.Popen("python demo.py")
>>> p3 = winctrlc.Popen("python demo.py")
>>> p2.send_ctrl_c()
>>> p1.send_ctrl_c()
>>> p3.send_ctrl_c()
Code
import socket
import subprocess
import time
import random
import signal, os, sys
class Popen:
_port = random.randint(10000, 50000)
_connection = ''
def _start_ctrl_c_wrapper(self, cmd):
cmd_str = "start \"\" python winctrlc.py "+"\""+cmd+"\""+" "+str(self._port)
subprocess.Popen(cmd_str, shell=True)
def _create_connection(self):
self._connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self._connection.connect(('localhost', self._port))
def send_ctrl_c(self):
self._connection.send(Wrapper.TERMINATION_REQ)
self._connection.close()
def __init__(self, cmd):
self._start_ctrl_c_wrapper(cmd)
self._create_connection()
class Wrapper:
TERMINATION_REQ = "Terminate with CTRL-C"
def _create_connection(self, port):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('localhost', port))
s.listen(1)
conn, addr = s.accept()
return conn
def _wait_on_ctrl_c_request(self, conn):
while True:
data = conn.recv(1024)
if data == self.TERMINATION_REQ:
ctrl_c_received = True
break
else:
ctrl_c_received = False
return ctrl_c_received
def _cleanup_and_fire_ctrl_c(self, conn):
conn.close()
os.kill(signal.CTRL_C_EVENT, 0)
def _signal_handler(self, signal, frame):
time.sleep(1)
sys.exit(0)
def __init__(self, cmd, port):
signal.signal(signal.SIGINT, self._signal_handler)
subprocess.Popen(cmd)
conn = self._create_connection(port)
ctrl_c_req_received = self._wait_on_ctrl_c_request(conn)
if ctrl_c_req_received:
self._cleanup_and_fire_ctrl_c(conn)
else:
sys.exit(0)
if __name__ == "__main__":
command_string = sys.argv[1]
port_no = int(sys.argv[2])
Wrapper(command_string, port_no)
这篇关于在 Windows 上将 ^C 发送到 Python 子进程对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!