开发者

perl: can I wait 15 minutes, and then if a key has not been pressed, do something?

开发者 https://www.devze.com 2023-04-09 17:29 出处:网络
here\'s my first ever perl program: I want to make sure that if I\'m away from my machine for a while, then this script ssh\'s to our main server and kills all my processes there. (I keep forgetting

here's my first ever perl program:

I want to make sure that if I'm away from my machine for a while, then this script ssh's to our main server and kills all my processes there. (I keep forgetting to kill them when I go for lunch and they hog vast amounts of cpu and memory).

I've got this far, and 15 minutes after the screensaver activates the killing starts.

#!/usr/bin/perl

my $cmd = "dbus-monitor --session \"type='signal',interface='org.gnome.ScreenSaver',member='ActiveChanged'\"";

open (IN, "$cmd |");

while (<IN>) {
    if (m/^\s+boolean true/) {
        print "*** Screensaver is active ***\n";
        print "*** Sleeping before megadeath....\n";
        sleep(15*60);
        print "*** killing all jla processes on anvil...\n";
        $result = `开发者_StackOverflow中文版ssh anvil pkill -u jla`;
        print "*** should all be dead\n";
        print $result;

    } elsif (m/^\s+boolean false/) {
        print "*** Screensaver is no longer active ***\n";
    }
}

But what I'd like is to wait 15 minutes while monitoring the keyboard. If say, the 'N' key gets pressed (in the terminal the script is running in), then I want to abort the killing and go back to monitoring the screensaver. This will give me an escape route if the screensaver comes on while I'm getting coffee.

Some sort of Bond-style countdown would be nice, too.

Actually even better would be to notice when the screensaver get unlocked, and stop the countdown if so, going back into monitoring mode. Then I don't even have to worry about remembering to press N.


If your perl has thread support, you could do something like this:

#!/usr/bin/perl

use warnings; use strict;

use threads;
use threads::shared;
use Term::ReadKey;

my $DONT_TERMINATE :shared;
my $TIMEOUT = 5;

threads->new( sub { wait_for_keypress('n', $TIMEOUT) })->detach;
threads->new( sub { countdown($TIMEOUT) })->join;

sub countdown {
    my ($timeout) = @_;

    while ( 1 ) {
        my $elapsed = time - $^T;
        last if $elapsed >= $timeout;
        return if $DONT_TERMINATE;
        print $timeout - $elapsed, "\n";
        sleep 1;
    }

    print "Killing some processes\n";
}

sub wait_for_keypress {
    my ($key, $timeout) = @_;
    my $input = ReadKey( $timeout );

    $DONT_TERMINATE = (defined($input) and ($input eq $key));

    return;
}

If you don't have thread support, you can use Coro.

Note: I deleted my Coro example because it wasn't working properly. I'll post it again if I figure it out.


I'd use select (via IO::Select), which lets you check if a filehandle has ready data. However, you can't use "buffered" IO operators like <> with select, so this is more complicated than you might like (You have to use sysread and maintain your own buffer). Here's how to watch the screensaver activity, and do something if it's been on for 15 minutes.

use IO::Select;
my $s = IO::Select->new();
# ... Start dbus-monitor as above ...
$s->add(\*IN);

my $buf = '';
my $started = 0;
my $waitfor = 15*60;
while ( 1 ) {
    # Read from all ready filehandles (timeout if none ready after 1 sec)
    foreach my $fh ( @ready = $s->can_read(1) ) {
        sysread($fh, $buf, 128, length($buf));
    }
    # Handle each complete line of input
    while ( $buf =~ s/^(.+)\n// ) {
        my $line = $1
        # ... Do something ...
        if ( $line =~ m/^\s+boolean (true|false)/ ) {
            if ( $1 eq 'true' ) { $started = time; print "Starting timer\n" }
            else { $started = 0; print "Canceled timer\n" }
        }
    }
    next unless $started;

    # Screensaver is on, how long has it been running?
    my $timeleft = $started+$waitfor-time;
    if ( $timeleft <= 0 ) {
        print "The screensaver has been on for at least 15 minutes\n";
        # ... Do something ...
        $started = 0; # Don't do this again until the screensaver is restarted
    }
    else {
        # You can print out an updated countdown
        print "$timeleft seconds left\n";
    }
}

I haven't tested this at all, but it might be enough for you to make it work.

P.S. It won't work on Windows, where select only works on sockets.


Sinan's and nandhp's solutions will work for this task. threads and select are powerful tools in the Perl programmer's arsenal, but I'd be reluctant to suggest them for somebody's "first ever perl (sic) program". So I'll suggest another approach.

To oversimplify the statement of this problem, we want to do something (fire a command to kill processes on a remote sever) when something else happens (the screen saver has been active for 15 minutes).

 use strict;
 use warnings;

 initialize_program();
 until (something_happens()) {
     sleep 60;
 }
 do_something();
 exit;

The do_something part is straightforward:

 sub do_something {
    print "*** killing all jla processes on anvil...\n";
    $result = `ssh anvil pkill -u jla`;
    print "*** should all be dead\n";
    print $result;
 }

For the something_happens part of the program, I'd suggest sending the dbus-monitor output to a file in a background process, and reading from the file whenever you want to know the state of the screen saver. The dbus-monitor program produces output quite slowly, and reading from a Perl filehandle will tend to block (unless you learn about and use select).

I'm going to tweak the dbus-monitor command a little bit. This command will print out a timestamp every time the state of the screen saver changes:

 my $command = q[dbus-monitor --session "type='signal',interface='org.gnome.ScreenSaver',member='ActiveChanged'" | perl -ne 'print time," $_" if /boolean/'];

and we'll start our program by executing:

sub initialize_program {
    # broken into multiple lines for readability
    my $command = q[dbus-monitor --session ]
            . q["type='signal',interface='org.gnome.ScreenSaver',member='ActiveChanged'"]
            . q[ | perl -ne 'print time," $_" if /boolean/'];

    system("$command > /tmp/screensavermonitor &");
}

Now to see whether and for how long the screen saver is active, we parse /tmp/screensavermonitor every once in a while.

sub something_happens {
    open (my $fh, '<', '/tmp/screensavermonitor') or return do { warn $!;0 };
    my @output = <$fh>;
    close $fh;

    # we only care about the last output
    my $state = pop @output;
    if (!defined $state) {
         # maybe there's no output yet
         return 0;
    }

    if ($state =~ /false/) {
        # screensaver is not active
        return 0;   # event hasn't happened yet
    }

    if ($state =~ /true/) {
        # screensaver is active -- but for how long?
        # start time (in seconds since the epoch) is included in output
        my ($screensaver_start_time) = ($state =~ /(\d+)/);
        if (time - $screensaver_start_time >= 15 * 60) {
            return 1;
        } else {
            return 0;
        }
    }
    return 0;
}


As mob said, threads and select are overcomplicating this a bit. So here's something nice and simple with Term::ReadKey, which lets you do what you asked for in the first place: Wait for a key to be pressed, but timeout if no key is pressed within 15 minutes.

#!/usr/bin/env perl

use strict;
use warnings;

use Term::ReadKey;
my $cmd = "dbus-monitor --session \"type='signal', interface='org.gnome.ScreenSaver',member='ActiveChanged'\"";

open(IN, "$cmd |");

ReadMode 'cbreak';    # return keypress immediately without waiting for "Enter"

while (<IN>) {
  if (m/^\s+boolean true/) {
    print "*** Screensaver is active ***\n";
    print "*** Sleeping before megadeath....\n";

    my $key = ReadKey 900;    # timeout after 900 seconds = 15 minutes
    if (defined $key) {
      print "*** A key was pressed; megadeath averted\n";
    } else {
      print "*** killing all jla processes on anvil...\n";
      my $result = `ssh anvil pkill -u jla`;
      print "*** should all be dead\n";
      print $result;
    }
  } elsif (m/^\s+boolean false/) {
    print "*** Screensaver is no longer active ***\n";
  }
}

ReadMode 'restore';    # back to normal input mode

(Code is syntactically correct, but has not been run, so it is not fully tested. You may want to also set the 'noecho' ReadMode in addition to 'cbreak' to prevent the keypress which disables megadeath from appearing on the screen.)

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号