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

发布于 2024-12-07 13:55:25 字数 984 浏览 1 评论 0 原文

这是我的第一个 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。

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 = `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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

错爱 2024-12-14 13:55:25

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

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.

大姐,你呐 2024-12-14 13:55:25

我会使用 select< /a> (通过 IO::Select),其中让你检查如果文件句柄有准备好的数据。但是,您不能将“缓冲”IO 运算符(例如 <>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 仅适用于套接字。

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.

巡山小妖精 2024-12-14 13:55:25

Sinan 和 nandhp 的解决方案将适用于这项任务。 threadsselect 是 Perl 程序员武器库中的强大工具,但我不愿意将它们推荐给某人的“第一个 Perl(原文如此)程序”。所以我会建议另一种方法。

为了过度简化这个问题的描述,我们想要在发生其他事情(屏幕保护程序已激活 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;
}

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;
}
表情可笑 2024-12-14 13:55:25

正如 mob 所说,线程和 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

(代码在语法上是正确的,但尚未运行,因此未经过充分测试。除了“cbreak”之外,您可能还需要设置“noecho”ReadMode,以防止按下导致禁用超级死亡的按键出现在屏幕上。)

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.)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文