IIRC, Ctrl-C is etx. Thus send \x03.
Hello,
I have a script that uses subprocess.call to start an interactive program (i.e. database client). I would like to forward ctrl-c to the client application instead of terminating the entire subprocess. Is there any way to do it?
Thanks.
I have a question about sending "ctrl-c" type events to stdin on Windows. I googled around and even StackOverflow was full of dead ends.
Let's consider a simple program:
import subprocess TSHARK_PATH = "C:\\Program Files\\Wireshark\\tshark.exe" OUTPUT_FILE_NAME = "captured_packets.pcap" OUTPUT_DIR = "C:\\captured_packets_dir\\" INTERFACE_NO = "1" #as per "tshark -D" PACKET_FILTER = "icmp" # just an example OUTPUT_FILE_SIZE_LIMIT = "200" #in kB # here we do some preparation tshark = subprocess.Popen([TSHARK_PATH, "-i", INTERFACE_NO, "-b", "filesize:"+OUTPUT_FILE_SIZE_LIMIT, "-f", PACKET_FILTER, "-w", OUTPUT_DIR+OUTPUT_FILE_NAME]) # packet capturing is running, we do some other actions tshark.terminate() # do some post processing using captured files
Here is the problem:
-
If I do the same from console (manually) everything is fine -> but the Tshark is stopped by ctrl-c
-
If I do this from Python script (automatically) I get an "partial packet" error -> the Tshark is stopped by terminate()
Is there a way to send "ctrl-c" to the process using Subprocess? I know that Popen can be run with stdin=subprocess.PIPE, but simple write() won't do the trick.
Or maybe I'm doing this backwards and this is not the right solution to do this? I'm using Tshark because it's easier to use this tool for all the heavy lifting.
Windows seem to be rather unfriendly towards Python automation of command line tools.
This works:
import subprocess
import time
import win32api
import win32con
proc = subprocess.Popen("ping -t localhost", stdin=subprocess.PIPE)
time.sleep(3) # just so it runs for a while
print "sending ctrl c"
try:
win32api.GenerateConsoleCtrlEvent(win32con.CTRL_C_EVENT, 0)
proc.wait()
except KeyboardInterrupt:
print "ignoring ctrl c"
print "still running"
It sends ctrl-c to all processes that share the console of the calling process but then ignores it in the python process with an exception handler. You can't target the subprocess directly because that signal cannot be generated for process groups.
ctypes version
import subprocess
import time
import ctypes
proc = subprocess.Popen("ping -t localhost", stdin=subprocess.PIPE)
time.sleep(3) # just so it runs for a while
try:
ctypes.windll.kernel32.GenerateConsoleCtrlEvent(0, 0)
proc.wait()
except KeyboardInterrupt:
print "ignoring ctrlc"
print "still running"
ctrl-break version
This can target the process specifically but with ping ctrl-break doesn't actually cause it to terminate. You may be able to use it with tshark though.
import subprocess
import time
import ctypes
proc = subprocess.Popen("ping -t localhost", stdin=subprocess.PIPE,
creationflags=512) # CREATE_NEW_PROCESS_GROUP = 512
time.sleep(3) # just so it runs for a while
ctypes.windll.kernel32.GenerateConsoleCtrlEvent(1, proc.pid) # CTRL_BREAK_EVENT = 1
proc.wait()
On Unix, the terminate() functions sends SIGINT, which allows the process to clean up after itself before exiting, while the kill() method sends SIGKILL which ends it immediately. However on Windows both methods call the Windows API function TerminateProcess which straight up kills the process and does not allow cleanup, so tshark never gets a chance to write its files. See here and here.
I'm not much of a Windows expert, but have you tried os.kill(process.pid, signal.CTRL_C_EVENT)?
EDIT: Looking at the documentation for subprocess, it also looks like you need to create the process with the CREATE_NEW_PROCESS_GROUP flag to allow you to send signals to it. As an alternative to os.kill, there is also the slightly cleaner tshark.send_signal(signal.CTRL_C_EVENT).
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_EVENTis 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_EVENTis 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)
New answer:
When you create the process, use the flag CREATE_NEW_PROCESS_GROUP. And then you can send CTRL_BREAK to the child process. The default behavior is the same as CTRL_C, except that it won't affect the calling process.
Old answer:
My solution also involves a wrapper script, but it does not need IPC, so it is far simpler to use.
The wrapper script first detaches itself from any existing console, then attach to the target console, then files the Ctrl-C event.
import ctypes
import sys
kernel = ctypes.windll.kernel32
pid = int(sys.argv[1])
kernel.FreeConsole()
kernel.AttachConsole(pid)
kernel.SetConsoleCtrlHandler(None, 1)
kernel.GenerateConsoleCtrlEvent(0, 0)
sys.exit(0)
The initial process must be launched in a separate console so that the Ctrl-C event will not leak. Example
p = subprocess.Popen(['some_command'], creationflags=subprocess.CREATE_NEW_CONSOLE)
# Do something else
subprocess.check_call([sys.executable, 'ctrl_c.py', str(p.pid)]) # Send Ctrl-C
where I named the wrapper script as ctrl_c.py.
It's because of the design of the Python interpreter and interactive session.
Ctrl + C sends a signal, SIGINT, to the Python process, which the Python interpreter handles by raising the KeyboardInterrupt exception in the currently-running scope.
If the interpreter is running in an interactive session (i.e. by running python or python3 at the console), then the exception in the current function is printed and you return to the Python prompt. If the interpreter is running a script (e.g. by python3 my_script.py), then unless the KeyboardInterrupt is handled by the script, the whole program will stop when the exception is raised.
It's worth pointing out from the docs that you need to handle KeyboardInterrupt explicitly, even if you already have a catch for Exception:
The exception inherits from BaseException so as to not be accidentally caught by code that catches Exception and thus prevent the interpreter from exiting.
I think you can just send the Linux equivalent, signal.SIGINT (the interrupt signal).
(Edit: I used to have something here discouraging the use of this strategy for controlling subprocesses, but on more careful reading it sounds like you've already decided you need control-C in this specific case... So, SIGINT should do it.)
Maybe I misunderstand something, but the way you do it it is difficult to get the desired result.
Whatever buff is, you query it first, then use it in the context of Popen() and then you hope that by maciv lineList fills itself up.
What you probably want is something like
logfile = open("mylogfile", "a")
p = subprocess.Popen(['vprobe', '/vprobe/myhello.emt'], stdout=subprocess.PIPE, buff, universal_newlines=True, preexec_fn=os.setsid)
for line in p.stdout:
logfile.write(line)
if re.search("done", line):
break
print "waiting"
os.kill(p.pid, signal.CTRL_C_EVENT)
This gives you a pipe end fed by your vprobe script which you can read out linewise and act appropriately upon the found output.
» pip install console-ctrl
Here you can use python's "PyAutoGui" module too. It's very simple and very short to code.
Ex.
pyautogui.hotkey('ctrl', 'c') # ctrl-c to copy
pyautogui.hotkey('ctrl', 'v') # ctrl-v to paste
Wow this is lying here from 2019.
so I used this code and it worked
win32clipboard.OpenClipboard()
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardText('')
win32clipboard.CloseClipboard()
kb.send('control+a')
time.sleep(.01)
kb.send('control+c')
time.sleep(.3)
win32clipboard.OpenClipboard()
data = win32clipboard.GetClipboardData()
win32clipboard.CloseClipboard()
note that it wouldn't work with capital C in the hotkey
kb.send('control+C')