perl:我可以等15分钟,然后如果没有按下按键,可以做点什么吗?

时间:2011-09-28 16:56:03

标签: perl io wait screensaver

这是我的第一个perl程序:

我想确保如果我离开我的机器一段时间,那么这个脚本ssh到我们的主服务器并杀死我在那里的所有进程。 (当我去吃午餐时,我一直忘记杀死他们,他们占用了大量的CPU和记忆)。

我已经走到这一步了,屏幕保护程序激活杀戮开始后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 个答案:

答案 0 :(得分:4)

如果你的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示例,因为它无法正常工作。如果我搞清楚的话,我会再发一次。

答案 1 :(得分:3)

我使用select(通过IO::Select),它可以让您检查文件句柄是否有现成数据。但是,您不能使用<>select之类的“缓冲”IO运算符,因此这比您想要的更复杂(您必须使用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";
    }
}

我根本没有对此进行过测试,但它可能足以让它发挥作用。

<子> P.S。它不适用于Windows,select仅适用于套接字。

答案 2 :(得分:3)

Sinan和nandhp的解决方案将适用于此任务。 threadsselect是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命令。每次屏幕保护程序状态发生变化时,此命令都会打印出时间戳:

 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 :(得分:1)

正如暴徒所说,线程和select使这一点过于复杂。所以这里有Term::ReadKey的简单和简单的东西,它可以让你首先做你要求的事情:等待按键,但如果在15分钟内没有按下按键,则超时。< / p>

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

(代码在语法上是正确的,但尚未运行,因此未经过全面测试。您可能还需要设置&#39; noecho&#39; ReadMode以及&#39; cbreak&#39;阻止禁止兆字节出现在屏幕上的按键。)