walkingthecow has asked for the wisdom of the Perl Monks concerning the following question:

Hey fellow monks,
I am sitting on a solaris 8 machine with perl version 5.005_03. I am trying to capture CTRL+C (because some people use it to paste when using ssh), and I would like to ask the user if they'd like to quit the script rather than actually quitting right away. I'm just wondering how to do it? The way I currently have does work, however, when saying "N" to resume you have to hit enter 4 times before resuming (at least that is how it is for me)... So here is what I have thus far, any help is so greatly appreciated!!

#!/usr/bin/perl -w use strict; $SIG{INT} = \&catch_int; print "eat tacos for sustanence\n"; chomp(my $ans=<STDIN>); print "eat ham for sustanence\n"; chomp($ans=<STDIN>); sub catch_int { while (1) { print "Do you really want to quit? "; chomp(my $ans=<STDIN>); if ($ans =~ /[Yy]/) { exit(0); } elsif ($ans =~ /[Nn]/) { print "Resuming...\n"; last; } else { next; } } }

Replies are listed 'Best First'.
Re: Have $SIG{INT} ask if user wants to quit
by lakshmananindia (Chaplain) on Mar 11, 2009 at 07:31 UTC

    The below one works for me

    use strict; use warnings; $SIG{INT}=\&handler; sub handler { while(1) { print "Do you want to really quit....."; chomp(my $a=<STDIN>); if($a =~ /y/) { exit(0); } elsif($a =~ /n/) { return; } else { next; } } } while(1) { }

    Is this you need???


    --Lakshmanan G.

    Your Attempt May Fail, But Never Fail To Make An Attempt

      It's the oddest thing, but when I use this bit of code with a small and simple script, it works as expected. However, when I use it in my script with 6,500 lines of code, it works, but I have to hit enter three times after hitting "N" to resume the script (Y and every other key work as expected...). There must be something wrong in my code, but it's far too much to put in here. Anyway, would you agree that this is a more concise way of figuring out if user is entering Y or N, and are there other better ways of doing it??

      # define signal handler to catch contol-c $SIG{'INT'} = 'catch_int'; sub catch_int { my $ans; system("clear"); while ($ans !~ /[Nn]/) { print "Do you want to exit [y or n]? "; chomp($ans=<STDIN>); if ($ans =~ /[Yy]/) { exit(0); } } }

        You do quite a lot of work (calling some arbitary external program, writing to the current default handle, and reading STDIN) inside a signal handler. This will surely bite you with any old perl (before 5.7.3), and I won't bet that newer perls will have no problems. Don't put much work into signal handlers, just set a flag and handle it in the main loop -- or just exit() or die() immediately. See also Signals.

        Alexander

        -- Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)
Re: Have $SIG{INT} ask if user wants to quit
by ig (Vicar) on Mar 11, 2009 at 08:40 UTC

    While I am not certain I suspect that the problem arises because the signal is interrupting the read on STDIN then in the signal handler you are reading from STDIN and then, if the user chooses not to quit, the original read resumes, but now its input buffers have been confused by the read in the interrupt handler.

    One way to avoid reading from STDIN in the signal handler is as follows:

    #!/usr/bin/perl use strict; use warnings; foreach my $prompt ( "eat tacos for sustenance", "eat ham for sustenance", ) { eval { local $SIG{INT} = sub { die "caught sig\n"; }; print "$prompt: "; chomp(my $ans=<STDIN>); print "\nans = $ans\n"; }; if($@ eq "caught sig\n") { print "\nDo you really want to quit? "; my $ans = <STDIN>; exit(1) if($ans =~ m/^y/i); redo; } }

    In this case, the signal handler terminates the eval, terminating the read. The following code then prompts the user and either terminates the program or repeats the loop block, depending on the answer.

      I completely agree with you! Here's an example of what you mean below. If control-c is captured when asking for input, then script will crash. However, if control-c is captured during output, everything goes as expected... I guess I am just breaking stdin and it does not know how to get back there... I'm not sure how in my own code to remedy this, since there are a lot of prompts, all broken by output.
      #!/usr/bin/perl -w $SIG{'INT'} = 'catch_int'; sub catch_int { my $int_ans; system("\n"); while ($int_ans !~ /[Nn]/) { print "Do you want to exit [y or n]? "; $int_ans=<STDIN>; if ($int_ans =~ /[Yy]/) { die "\nCaught SIGINT -- Program stopped by user\n"; } } } print "Please enter a choice: "; chomp(my $choice=<STDIN>); if ($choice =~ /[A-Z]+/i) { for (my $i = 0; $i < 10000; $i++) { print "Good choice!\n"; } }

        First you should make sure you are very familiar with Signals. Then you need to carefully consider the quirks of your platform(s). There are many, sometimes subtle and sometimes critical differences between the various operating systems.

        There are two possibilities that will generally be "safe": the first is to do nothing but set a flag in your interrupt handler and take the appropriate action elsewhere; the other is to die in your signal handler and, where appropriate, run code in an eval to handle the abnormal termination, as in the example I showed previously. Doing anything else in the signal handler tends to be problematic and tricky, as you are finding out.

        If your main program consists of a tight loop with rapid iterations, the first approach can work well. Just add a test for the flag in the main loop and it will be tested reasonably soon after the signal is caught.

        If you have some long running sections of code in which it is inconvenient to scatter checks for a flag and you want a prompt response to ^C then the second approach is probably necessary. It allows you to terminate long running sections without explicit coding within the section.

        If the main problem is interrupts while prompting for interactive input, as in your example, then I suggest creating a subroutine to prompt and collect the response with the subroutine implemented similarly to my previous example. While it may be tedious to revise your program if there are many instances, it will be quite straight forward and will not require significant change to the structure.

        Otherwise, you can investigate ways to restore the I/O layers (which may differ depending on platform and perl compile and run-time options) to a functional state after doing I/O in the signal handler. I don't know enough about the I/O implementations to do anything other than steer clear of I/O in signal handlers myself. Sorry...

        If you can post representative samples of your actual code, you may get some further helpful suggestions.

Re: Have $SIG{INT} ask if user wants to quit
by repellent (Priest) on Mar 11, 2009 at 17:03 UTC