Better handling of KeyboardInterrupt in cmd.Cmd command line interpreter - python

While using python's cmd.Cmd to create a custom CLI, how do I tell the handler to abort the current line and give me a new prompt?
Here is a minimal example:
# console_min.py
# run: 'python console_min.py'
import cmd, signal
class Console(cmd.Cmd):
def __init__(self):
cmd.Cmd.__init__(self)
self.prompt = "[test] "
signal.signal(signal.SIGINT, handler)
def do_exit(self, args):
return -1
def do_EOF(self, args):
return self.do_exit(args)
def preloop(self):
cmd.Cmd.preloop(self)
self._hist = []
self._locals = {}
self._globals = {}
def postloop(self):
cmd.Cmd.postloop(self)
print "Exiting ..."
def precmd(self, line):
self._hist += [ line.strip() ]
return line
def postcmd(self, stop, line):
return stop
def emptyline(self):
return cmd.Cmd.emptyline(self)
def handler(self, signum, frame):
# self.emptyline() does not work here
# return cmd.Cmd.emptyline(self) does not work here
print "caught ctrl+c, press return to continue"
if __name__ == '__main__':
console = Console()
console.cmdloop()
Further help is greatly appreciated.
Original Question and more details:
[Currently the suggestions below have been integrated into this question -- still searching for an answer. Updated to fix an error.]
I've since tried moving the handler to a function outside the loop to see if it were more flexible, but it does not appear to be.
I am using python's cmd.Cmd module to create my own command line interpreter to manage interaction with some software. I often press ctrl+c expecting popular shell-like behavior of returning a new prompt without acting on whatever command was typed. However, it just exits. I've tried to catch KeyboardInterrupt exceptions at various points in the code (preloop, etc.) to no avail. I've read a bit on sigints but don't quite know how that fits in here.
UPDATE: From suggestions below, I tried to implement signal and was able to do so such that ctrl+c now doesn't exit the CLI but does print the message. However, my new issue is that I cannot seem to tell the handler function (see below) to do much beside print. I would like ctrl+c to basically abort the current line and give me a new prompt.

I'm currently working on a creation of a Shell by using the Cmd module. I've been confronted with the same issue, and I found a solution.
Here is the code:
class Shell(Cmd, object)
...
def cmdloop(self, intro=None):
print(self.intro)
while True:
try:
super(Shell, self).cmdloop(intro="")
break
except KeyboardInterrupt:
print("^C")
...
Now you have a proper KeyboardInterrupt (aka CTRL-C) handler within the shell.

Instead of using signal handling you could just catch the KeyboardInterrupt that cmd.Cmd.cmdloop() raises. You can certainly use signal handling but it isn't required.
Run the call to cmdloop() in a while loop that restarts itself on an KeyboardInterrupt exception but terminates properly due to EOF.
import cmd
import sys
class Console(cmd.Cmd):
def do_EOF(self,line):
return True
def do_foo(self,line):
print "In foo"
def do_bar(self,line):
print "In bar"
def cmdloop_with_keyboard_interrupt(self):
doQuit = False
while doQuit != True:
try:
self.cmdloop()
doQuit = True
except KeyboardInterrupt:
sys.stdout.write('\n')
console = Console()
console.cmdloop_with_keyboard_interrupt()
print 'Done!'
Doing a CTRL-c just prints a new prompt on a new line.
(Cmd) help
Undocumented commands:
======================
EOF bar foo help
(Cmd) <----- ctrl-c pressed
(Cmd) <------ctrl-c pressed
(Cmd) ddasfjdfaslkdsafjkasdfjklsadfljk <---- ctrl-c pressed
(Cmd)
(Cmd) bar
In bar
(Cmd) ^DDone!

You can catch the CTRL-C signal with a signal handler. If you add the code below, the interpreter refuses to quit on CTRL-C:
import signal
def handler(signum, frame):
print 'Caught CTRL-C, press enter to continue'
signal.signal(signal.SIGINT, handler)
If you don't want to press ENTER after each CTRL-C, just let the handler do nothing, which will trap the signal without any effect:
def handler(signum, frame):
""" just do nothing """

In response to the following comment in this answer:
This appears to be converging on a solution, but I don't know how to integrate it into my own code (above). I have to figure out the 'super' line. I need to try and get this working at some point in the future.
super() would work in this answer if you have your class extend object in addition to cmd.Cmd. Like this:
class Console(cmd.Cmd, object):

I wanted to do the same, so I searched for it and created basic script for understanding purposes, my code can be found on my GitHub
So, In your code,
instead of this
if __name__ == '__main__':
console = Console()
console.cmdloop()
Use this,
if __name__ == '__main__':
console = Console()
try:
console.cmdloop()
except KeyboardInterrupt:
print ("print sth. ")
raise SystemExit
Hope this will help you out. well, it worked for me. πŸ˜€

You can check out the signal module: http://docs.python.org/library/signal.html
import signal
oldSignal = None
def handler(signum, frame):
global oldSignal
if signum == 2:
print "ctrl+c!!!!"
else:
oldSignal()
oldSignal = signal.signal(signal.SIGINT, handler)

Just add this inside the class Console(cmd.Cmd):
def cmdloop(self):
try:
cmd.Cmd.cmdloop(self)
except KeyboardInterrupt as e:
self.cmdloop()
Forget all the other stuff. This works. It doesn't make loops inside of loops. As it catches KeyboardInterrupt it calls do_EOF but only will execute the first line; since your first line in do_EOF is return do_exit this is fine.
do_exit calls postloop.
However, again, it only executes the first line after cmd.Cmd.postloop(self). In my program this is print "\n". Strangely, if you SPAM ctrl+C you will eventually see it print the 2nd line usually only printed on ACTUAL exit (ctrl+Z then enter, or typing in exit).

I prefer the signal method, but with just pass. Don't really care about prompting the user with anything in my shell environment.
import signal
def handler(signum, frame):
pass
signal.signal(signal.SIGINT, handler)

Related

Is there a way to never exit on KeyboardInterrupt in python?

I'm creating sort of a interactive command line in python. I have something like this:
def complete_menu():
while True:
cmd = input('cmd> ')
if cmd == "help":
print('help')
elif cmd == "?":
print('?')
When the user presses CTRL-C, instead of exiting the program I'm trying to make it so that it prints "please type exit to exit" and goes back to the while True. I have something like this at the moment:
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print('Please use exit to exit')
complete_menu()
Although this works, there is a number of issues. For one, when CTRL-C is pressed the first time, it prints out the text and works perfectly. However, the second time the user presses CTRL-C, it exists with a bunch of messy text like any other program after pressing CTRL-C. Can this be fixed?
The better way to do this is to register a signal handler:
import signal
def handler(signum, frame):
print("Please use exit to exit")
# or: just call sys.exit("goodbye")
...
def main():
signal.signal(signal.SIGINT, handler) # prevent "crashing" with ctrl+C
...
if __name__ == "__main__":
main()
Now when a Ctrl+C is received in your code, instead of a KeyboardInterrupt exception being raised, the function handler will be executed. This is a basic example, customize the code within handler to do what you want.
Note: My recommendation is to actually let the user exit with Ctrl+C, i.e. execute any cleanup code that you might need to run and then call sys.exit here. Programs that require a stronger signal to kill are annoying.

How to terminate main program when thread ends? Still getting waiting for process to detach in python?

I am having a main program which is defined like this:
main.py
def main():
try:
registry.start_server()
except:
print("Shutting down the program")
pass
if __name__ == '__main__':
main()
registry.start_server() is the method in another module which looks like this:
def start_server():
t_server = threading.Thread(target=server.start)
t_server.start()
try:
t_server.join()
except KeyboardInterrupt:
print("Error")
raise ValueError
finally:
fp.close()
server.start is the method in another module which does some listening work in a while(True) manner. I am not sure how to stop the whole program when clicking Stop in PyCharm which is Ctrl + C (Signal). I tried with Event but without success. I get to the main.py by raising an exception when the signal gets caught but that does not terminate the whole program. It shows Waiting for program to detach. The only way is to use SIGKILL. I don't understand where does the program keeps hanging? I have also tried calling sys.exit(0) when the signal gets caught and creating the thread as Deamon but that didnt help either.
EDIT
While True method in another module
def start(self, event):
try:
while True:
if event.is_set():
if self.pubsub.channels:
print("It enters here")
message = self.pubsub.get_message(True)
if message:
.
.
.
else:
return
To solve the problem, all you need to do is:
let the child-thread exit, and
let main thread join the child-thread.

handling unexpected shutdown python

is there any way to catch an exception for an unexpected shutdown of program in python ?
let say I am running a python script in a console then I don't press control+c to stop the program but rather just click the close button of the console is there any way to catch the error before the console close?
like this:
try:
print("hello")
except KeyboardInterrupt:
exit()
except UnexpectedClose:
print("unexpected shutoff")
exit()
thanks in advance
Following the link I put in the comment above already and reading here that forced closing is sending a SIGHUP this modified version writes an output file when the terminal window is closed and the python process is "killed".
Note, I just combined information (as cited) available on SE.
import signal
import time
class GracefulKiller:
kill_now = False
def __init__(self):
signal.signal(signal.SIGINT, self.exit_gracefully)
signal.signal(signal.SIGTERM, self.exit_gracefully)
signal.signal(signal.SIGHUP, self.exit_gracefully)
def exit_gracefully(self,signum, frame):
with open('kill.txt', 'w') as fpntr:
fpntr.write('killed')
self.kill_now = True
if __name__ == '__main__':
killer = GracefulKiller()
while True:
time.sleep(1)
print("doing something in a loop ...")
if killer.kill_now:
break
print "End of the program. I was killed gracefully :)"

How to handle console exit and object destruction

Given this code:
from time import sleep
class TemporaryFileCreator(object):
def __init__(self):
print 'create temporary file'
# create_temp_file('temp.txt')
def watch(self):
try:
print 'watching tempoary file'
while True:
# add_a_line_in_temp_file('temp.txt', 'new line')
sleep(4)
except (KeyboardInterrupt, SystemExit), e:
print 'deleting the temporary file..'
# delete_temporary_file('temp.txt')
sleep(3)
print str(e)
t = TemporaryFileCreator()
t.watch()
during the t.watch(), I want to close this application in the console..
I tried using CTRL+C and it works:
However, if I click the exit button:
it doesn't work.. I checked many related questions about this but it seems that I cannot find the right answer..
What I want to do:
The console can be exited while the program is still running.. to handle that, when the exit button is pressed, I want to make a cleanup of the objects (deleting of created temporary files), rollback of temporary changes, etc..
Question:
how can I handle console exit?
how can I integrate it on object destructors (__exit__())
Is it even possible? (how about py2exe?)
Note: code will be compiled on py2exe.. "hopes that the effect is the same"
You may want to have a look at signals. When a *nix terminal is closed with a running process, this process receives a couple signals. For instance this code waits for the SIGHUB hangup signal and writes a final message. This codes works under OSX and Linux. I know you are specifically asking for Windows but you might want to give it a shot or investigate what signals a Windows command prompt is emitting during shutdown.
import signal
import sys
def signal_handler(signal, frame):
with open('./log.log', 'w') as f:
f.write('event received!')
signal.signal(signal.SIGHUP, signal_handler)
print('Waiting for the final blow...')
#signal.pause() # does not work under windows
sleep(10) # so let us just wait here
Quote from the documentation:
On Windows, signal() can only be called with SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, or SIGTERM. A ValueError will be raised in any other case.
Update:
Actually, the closest thing in Windows is win32api.setConsoleCtrlHandler (doc). This was already discussed here:
When using win32api.setConsoleCtrlHandler(), I'm able to receive shutdown/logoff/etc events from Windows, and cleanly shut down my app.
And if Daniel's code still works, this might be a nice way to use both (signals and CtrlHandler) for cross-platform purposes:
import os, sys
def set_exit_handler(func):
if os.name == "nt":
try:
import win32api
win32api.SetConsoleCtrlHandler(func, True)
except ImportError:
version = β€œ.”.join(map(str, sys.version_info[:2]))
raise Exception(”pywin32 not installed for Python ” + version)
else:
import signal
signal.signal(signal.SIGTERM, func)
if __name__ == "__main__":
def on_exit(sig, func=None):
print "exit handler triggered"
import time
time.sleep(5)
set_exit_handler(on_exit)
print "Press to quit"
raw_input()
print "quit!"
If you use tempfile to create your temporary file, it will be automatically deleted when the Python process is killed.
Try it with:
>>> foo = tempfile.NamedTemporaryFile()
>>> foo.name
'c:\\users\\blah\\appdata\\local\\temp\\tmpxxxxxx'
Now check that the named file is there. You can write to and read from this file like any other.
Now kill the Python window and check that file is gone (it should be)
You can simply call foo.close() to delete it manually in your code.

custom output when control-c is used to exit python script

I would like the user to use control-c to close a script, but when control-c is pressed it shows the error and reason for close (which make sense). Is there a way to have my own custom output to the screen rather than what is shown? Not sure how to handle that specific error.
You could use try..except to catch KeyboardInterrupt:
import time
def main():
time.sleep(10)
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
print('bye')
use the signal module to define a handler for the SIGINT signal:
import signal
import sys
def sigint_handler(signal_number, stack_frame):
print('caught SIGINT, exiting')
sys.exit(-1)
signal.signal(signal.SIGINT, sigint_handler)
raw_input('waiting...')
For general purpose code, handling the KeyboardInterrupt should suffice. For advanced code, such as threading, it is a whole different story. Here's a simple example.
http://docs.python.org/2/library/exceptions.html#exceptions.KeyboardInterrupt
try:
while 1:
x = raw_input("Type something or press CTRL+C to end: ")
print repr(x)
except KeyboardInterrupt:
print "\nWe're done here."

Categories