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

i am trying to run some maintenance scripts on my unix server. some of these can run for hours if not days. at the end of the script i write a log file including all the tracking information needed to evaluate the performance. many times these scripts need to be terminated early and therefore never reach the log portion of the script. i am looking for a way to accept keyboard input during runtime so i can still write the output file and then pick up later. does perl have a "keypressed" equivalent of pascal or even vb? any help or links to information would be appreciated. thnx in advance.


"A computer is almost human - except that it does not blame its mistakes on another computer."

Replies are listed 'Best First'.
Re: keyboard input during runtime...
by grinder (Bishop) on Aug 06, 2001 at 17:38 UTC
    If you have Term::ReadKey installed, it is a simple matter of saying

      ReadKey( -1 ); # or ReadLine( -1 )

    and then you can read from STDIN and if there is nothing waiting your script will continue. Note that this doesn't work on Win32. I have created the same functionality in the past using a watchdog file:

    my $watchdog = "./watchdog.@{[time]}.$$"; open WATCHDOG, ">$watchdog" or die "Cannot open $watchdog for output +: $!\n"; print WATCHDOG "This file may safely be deleted\n"; close WATCHDOG; while( $universe->evolve ) { if( !(++$iters % 100_000) ) { last unless -f $watchdog; } # do some more stuff } do_logging(); unlink $watchdog;

    Once your script is churning away, all you have to do is to find some way of deleting the file you created at the top of the script. Kill it, and your script can close down doing whatever housekeeping needs to be done.

    In the example, I show that the test be performed once every x iterations. For performance reasons, you don't really want to be statting the disk on every iteration. Tune this value to get the reactivity you require.

    I quite like this solution, as you don't need to install another module, it's eminently portable and other processes can kill the execution if need be.

    --
    g r i n d e r
Re: keyboard input during runtime...
by dragonchild (Archbishop) on Aug 06, 2001 at 17:55 UTC
    Since you're running on Unix and not Win32, this solution will work for you.

    What you want to do is to select on STDIN every so often. Say, every time a certain loop starts over or every section or whatever. The code you're looking for is:

    # Whenever you want to actually see if there's something waiting my $rin = ''; my $input = ''; vec($rin, fileno(STDIN), 1) = 1; $input = <STDIN> if select($rin, undef, undef, 0.1);
    That should work. It's best if you write a subroutine and return either undef (if nothing) or what was read. You can even chomp in the subroutine, as well.

    Oh - you'll have to hit RETURN so that the filesystem can pass in the values. There's no native keypressed-type thing. (You can get it with Term::ReadLine and the like, but that's more complex than I think you want.)

    And, this will not work under Win32 without significant changes, due to how select is implemented there. Unless you're planning on migrating, don't worry about it. :)

    ------
    /me wants to be the brightest bulb in the chandelier!

Re: keyboard input during runtime...
by tachyon (Chancellor) on Aug 06, 2001 at 18:06 UTC

    I would just add a process interrupt handler to your script. Then just zap the program and it will call a sub to do stuff like write your logfile before exiting.

    $SIG{INT} = \&zap; # trap interrupts and run &zap sub sub zap { print "You zapped me!\n"; # do some stuff die; } sleep 1 while 1;

    This code will obviously not finish until you zap it, like sleeping beauty it will keep on sleeping. When you zap it the $SIG{INT} handler calls the zap sub rather than just exiting allowing you to do whatever.

    Hope this helps.

    cheers

    tachyon

    s&&rsenoyhcatreve&&&s&n.+t&"$'$`$\"$\&"&ee&&y&srve&&d&&print

      I cannot really recommend this approach.

      If a second INT signal is received, zap will be called again. Since the housekeeping involves opening and writing to files things will start to go a little bezerk, since you will have two (or more) paths of execution running asynchronously. If files are being appended to, the results will be disastrous. Try running the following code:

      #! /usr/bin/perl -w use strict; my @a = (1..200000); $SIG{INT} = sub { open OUT, '>>logfile'; foreach( @a ) { print OUT "$_\n"; } close OUT; exit; }; 1 while 1;

      If you feed this an INT signal, followed by another one in less than the time it takes to write out the file you will some very interesting results in the logfile...

      --
      g r i n d e r
      That is a bad idea for many reasons.

      update: i'm on winblows 9x here, in case you was wonderin';)
      Also, exit from a sigint handler is also a bad idea, just as bad as a die, or a warn, or anything, which doesn't actually terminate the program like a kill.

      First, that is not the proper way to exit a program after capturing sigint, you gotta kill youself (i found out the hard way), like:

      $SIG{INT} = \&zap; sub zap { print "You zapped me!\n"; # do some stuff # you can't die; # kill 6 which is ABRT doesn't work (crash) # under strict in indigoperl for some reason kill 15, $$; # TERM = 15, forgot where you import it from } sleep 1 while 1;
      Second, because of the funky stuff that happens when you capture signals in perl, you can do a few things before killing yourself, but you can't go back into an infinite loop, cause things get pretty messed up. I was gonna suggest something like (code recycled from my personal tests):
      #!/usr/bin/perl -w use strict; use sigtrap 'handler' => \&CLEANUP, 'INT'; # $SIG{INT}=\&CLEANUP; =head1 modulus (%) explained =pod Binary ``%'' computes the modulus of two numbers. Given integer opera +nds $a and $b: If $b is positive, then $a % $b is $a minus the largest multiple of $b that is not greater than $a. If $b is negative, then $a % $b is $a minus the smallest multiple of $b that is not less than $a (i.e. the result will be less than or equal to zero). =cut $|++; # i like a free flow of ideas &theloopy; sub theloopy { my($a,$b)=(0,0); while(11) { $a = '|' if(($b % 4) == 1); #| $a = '/' if(($b % 4) == 2); #/ $a = '-' if(($b % 4) == 3); #- $a = '\\' if(($b % 4) == 0); #\ print "\r $a$a$a infinite loop"; select(undef,undef,undef,0.25); # sleep $b++; } } sub CLEANUP { print "\n caught \$SIG{INT}",@_,"\n"; my $shallwedie = <STDIN>; print "shall we die (666)\n"; $shallwedie = <STDIN>; chomp $shallwedie; # $SIG{'ABRT'} = DEFAULT; # just make sure # do your cleanup stuff # and then kill yourself #kill 'ABRT', $$; # ABRT = 6 if($shallwedie eq '666') { print "the dying\n"; kill 'TERM', $$; # TERM = 15 kill 6, $$; } else { print "the loopy\n"; &theloopy(); } #exit; }
      But as you can see if you run it, it does some weird things (or things i just don't understand, which might be the reason my example/strategy failed, in which case explanation is welcome ;)

      update: indeed having a label, and using a goto will not work, and it will crash perl. But what's this you say about about the handler returning someplace?

      Ok, it has been my experience that it returns to what your script was doing before, but another sigint will just kill the script, and i that is not desired behaviour.

      So there must be some kind of sigint counter?

       
      ___crazyinsomniac_______________________________________
      Disclaimer: Don't blame. It came from inside the void

      perl -e "$q=$_;map({chr unpack qq;H*;,$_}split(q;;,q*H*));print;$q/$q;"

        *laughs* If you just comment out the else-section where you call theloopy(), then you run just fine. :)

        I suspect the problem you had is that you were calling theloopy() from within the SIG{INT} handler. I tried doing a goto out, but something still keeps track that you're within the handling of a SIG{INT}, thus (I suspect) preventing the handling of another SIG{INT}. (Of course, that's pure speculation.)

        The reason why if you just let the SIG{INT} handler end if you don't want to do anything works just fine is that the handler will return back to where it was invoked from if nothing is done. I've used that to good use in other applications.

        ------
        /me wants to be the brightest bulb in the chandelier!

        i woke up this morning and had a moment of clarity. (thanks for /msging me the link here.) you need to reload the signal handler for it to work again. try this:

        #!/usr/local/bin/perl -w use strict; $|++; { # global count, closure my $count=0; # initialize count sub inc_count { ++$count } # increment count sub get_count { $count } # get count } $SIG{INT} = \&_SIGINT_; # set signal handler (CTRL-C) sub _SIGINT_ { # handle CTRL-C inc_count(); # increment global count $SIG{INT} = \&_SIGINT_; # reset signal handler (CTRL-C) }; while(1) { # infinite loop my $count = get_count(); # get the count of CTRL-C's $count % 2 # branch (just to do something) ? printf "\b\b\b\b\b\b\b\byes %2d",$count : printf "\b\b\b\b\b\b\b\b no %2d",$count; sleep 1; # take a cat nap $count >= 10 and last; # break loop (not so infinite) } print "\ndone!\n"; # et voila!
        </code> oh, and by the way, if you're running your code on windows, note that kill won't work as you think. specifically, kill will *always* abort the program, and force the exit code to the number you send. it will not send a signal to the program, like it does on unix.

        ~Particle

Re: keyboard input during runtime...
by physi (Friar) on Aug 06, 2001 at 18:02 UTC
    Don't be sure if this can help you:
    SIG{INT}=sub{exit}; END { open LOGFILE, ">filename"; print LOGFILE '...everything figured out up to now '; close LOGFILE; }
    Now you can terminate your script with CTRL-C and before it terminates, it will do the END block. So you can do all the nesessary stuff in there to save your results.

    update as tachyon mentioned, added the SIG{INT} line.

    ----------------------------------- --the good, the bad and the physi-- -----------------------------------

      Ah sorry this is wrong. CTRL-C this.

      sleep 1 while 1; END { print "Goodbye\n"}

      As you can see the CTRL-C zap is so fatal that the END block is not executed. You need to trap it with a $SIG{INT} as shown below. An END block will trap die though.

      die "I'm dead!\n"; END { print "Goodbye\n"}

      cheers

      tachyon

      s&&rsenoyhcatreve&&&s&n.+t&"$'$`$\"$\&"&ee&&y&srve&&d&&print