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

here is my first perl program in the world:

I want to make sure that if I leave my machine for a while, then this is a ssh script to our main server and will kill all my processes there. (I always forget to kill them when I go to dinner, and they dig up a huge amount of processor and memory).

I got this far, and 15 minutes after the screen saver activates the start of the kill.

#!/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 = `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 I would like to wait 15 minutes watching the keyboard. If, say, the "N" key is pressed (a script is running in the terminal), then I want to interrupt the kill and return to monitoring the screen saver. This will give me an escape route if the splash screen pops in while I get coffee.

Some kind of Bond style countdown will be nice too.

In fact, it would be even better to notice when the screensaver is unlocked, and stop the countdown, if so, return to monitoring mode. Then I don’t even have to worry about remembering to press N.

+4
source share
4 answers

The mob says that streams and select complicate this a bit. So, here is something pleasant and simple with Term :: ReadKey , which allows you to do what you asked for, first of all: wait for the key to be pressed, but wait time if no key is pressed for 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 

(The code is syntactically correct, but has not been run, so it has not been fully tested. You might also want to install the "noecho" ReadMode in addition to the "cbreak" to prevent a keystroke that disables the display of the megadata on the screen.)

+1
source

If your perl supports flow, you can 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 do not have thread support, you can use Coro .

Note. I deleted my Coro example because it did not work properly. I will send it again if I find out.

+4
source

I would use select (via IO::Select ), which allows me to check if the file descriptor has ready-made data. However, you cannot use "buffered" I / O operators such as <> with select , so this is harder than you might like (you have to use sysread and save your own buffer). Here, how to watch the screensaver activity, and do something if it was turned 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 have not tested this at all, but it may be enough for you to work.

<sub> PS It will not work on Windows, where select only works on sockets.

+3
source

Sinan and nandhp solutions will work for this task. threads and select are powerful tools in the arsenal of Perl programmers, but I would be reluctant to offer them to someone "the first ever program." Therefore, I propose a different approach.

To simplify the formulation of this problem, we want to do something (run the command to kill processes on the remote server) 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 simple:

  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 suggest sending dbus-monitor output to a file in the background and reading from the file whenever you want to know the status of the screen saver. The dbus-monitor program produces output rather slowly, and reading from a Perl file descriptor will tend to block (unless you recognize and use select ).

I will correct the dbus-monitor command a bit. This command will print a time stamp every time the screen saver state changes:

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

and we will run our program by doing:

 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 find out if the screen saver is active and how long it has been stored, we analyze /tmp/screensavermonitor from time to time.

 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 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; } 
+3
source

All Articles