perl: можно подождать 15 минут, а потом, если клавиша не нажата, что-то сделать? - PullRequest
4 голосов
/ 28 сентября 2011

вот моя первая Perl-программа:

Я хочу убедиться, что если я на некоторое время отошел от своей машины, то этот сценарий ssh ​​для нашего главного сервера и убьет все мои процессы там. (Я постоянно забываю убивать их, когда я иду на обед, и они тратят огромное количество ресурсов процессора и памяти).

У меня так далеко, и через 15 минут после активации заставки начинается убийство.

#!/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";
    }
}

Но я хотел бы подождать 15 минут, пока наблюдаю за клавиатурой. Если, скажем, нажата клавиша «N» (в терминале, в котором работает скрипт), то я хочу прервать убийство и вернуться к мониторингу заставки. Это даст мне выход, если заставка включится, когда я получаю кофе.

Какой-нибудь обратный отсчет в стиле Бонда тоже был бы хорош.

На самом деле, еще лучше было бы заметить, когда заставка разблокирована, и, если это так, остановить обратный отсчет, вернувшись в режим мониторинга. Тогда мне даже не нужно беспокоиться о том, чтобы не забыть нажать N.

Ответы [ 4 ]

4 голосов
/ 28 сентября 2011

Если у вашего perl есть поддержка потоков, вы можете сделать что-то вроде этого:

#!/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;
}

Если у вас нет поддержки потоков, вы можете использовать Coro .

Примечание. Я удалил пример Coro, поскольку он не работал должным образом.Я опубликую это снова, если я выясню это.

3 голосов
/ 28 сентября 2011

Решения Синан и Нандхпа будут работать для этой задачи.threads и select являются мощными инструментами в арсенале программиста Perl, но я бы неохотно предлагал их для чьей-то "первой в мире программы на Perl (sic)".Поэтому я предложу другой подход.

Чтобы упростить формулировку этой проблемы, мы хотим что-то сделать (запустить команду для уничтожения процессов на удаленном сервере), когда что-то еще происходит (заставка активнав течение 15 минут).

 use strict;
 use warnings;

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

Часть do_something проста:

 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;
 }

Для части программы something_happens я бы предложил отправить dbus-monitorвывод в файл в фоновом режиме и чтение из файла всякий раз, когда вы хотите узнать состояние хранителя экрана.Программа dbus-monitor производит вывод довольно медленно, и чтение из файлового дескриптора Perl будет иметь тенденцию блокировать (если вы не узнаете и не используете select).

Я собираюсь настроить команду dbus-monitor aнемного.Эта команда будет печатать временную метку каждый раз, когда изменяется состояние экранной заставки:

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

, и мы запустим нашу программу, выполнив:

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 &");
}

Теперь, чтобы увидеть,на сколько активна заставка, мы время от времени разбираем /tmp/screensavermonitor.

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;
}
3 голосов
/ 28 сентября 2011

Я бы использовал select (через IO::Select), что позволяет проверить, есть ли в дескрипторе файла готовые данные.Однако вы не можете использовать «буферизованные» операторы ввода-вывода, такие как <> с select, так что это сложнее, чем вы хотели бы (вы должны использовать sysread и поддерживать свой собственный буфер),Вот как наблюдать за работой заставки и делать что-то, если она была включена в течение 15 минут.

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";
    }
}

Я вообще не проверял это, но для вас этого может быть достаточно, чтобы заставить его работать.

PS Это не будет работать в Windows, где select работает только на сокетах.

1 голос
/ 29 сентября 2011

Как сказал моб, потоки и select немного усложняют это. Итак, вот кое-что приятное и простое с Term :: ReadKey , которое в первую очередь позволяет вам делать то, что вы просили: дождаться нажатия клавиши, но время ожидания, если ни одна клавиша не нажата в течение 15 минут.

#!/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

(Синтаксически правильный код, но он не был запущен, поэтому он не полностью протестирован. Возможно, вы также захотите установить режим чтения noecho в дополнение к cbreak, чтобы предотвратить нажатие клавиши, которая отключает мегадатчик на экран.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...