4 Stimmen

perl: Kann ich 15 Minuten warten und dann, wenn eine Taste nicht gedrückt wurde, etwas tun?

Hier ist mein allererstes Perl-Programm:

Ich möchte sicherstellen, dass dieses Skript, wenn ich für eine Weile von meinem Rechner weg bin, per SSH auf unseren Hauptserver zugreift und alle meine Prozesse dort beendet. (Ich vergesse immer wieder, sie zu beenden, wenn ich zum Mittagessen gehe, und sie beanspruchen Unmengen an CPU und Speicher).

Ich bin so weit gekommen, und 15 Minuten nachdem der Bildschirmschoner aktiviert wurde, beginnt das Töten.

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

Aber ich würde gerne 15 Minuten warten, während ich die Tastatur überwache. Wenn, sagen wir, die Taste "N" gedrückt wird (in dem Terminal, in dem das Skript läuft), dann möchte ich die Tötung abbrechen und zur Überwachung des Bildschirmschoners zurückkehren. So habe ich eine Ausweichmöglichkeit, falls der Bildschirmschoner anspringt, während ich mir einen Kaffee hole.

Eine Art Countdown im Stil von Bond wäre auch schön.

Noch besser wäre es, zu erkennen, wenn der Bildschirmschoner entsperrt wird, und den Countdown zu stoppen, wenn dies der Fall ist, und wieder in den Überwachungsmodus zu wechseln. Dann bräuchte ich mir keine Gedanken mehr zu machen, ob ich N drücken muss.

4voto

Sinan Ünür Punkte 114993

Wenn Ihr perl über eine Thread-Unterstützung verfügt, könnten Sie so vorgehen:

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

Wenn Sie keine Thread-Unterstützung haben, können Sie Coro .

Hinweis: Ich habe mein Coro-Beispiel gelöscht, weil es nicht richtig funktioniert hat. Ich werde es wieder posten, wenn ich es herausgefunden habe.

3voto

nandhp Punkte 4460

Ich würde die select (über IO::Select ), mit der Sie prüfen können, ob ein Filehandle fertige Daten enthält. Sie können jedoch keine "gepufferten" IO-Operatoren wie <> con select Das ist komplizierter, als Ihnen lieb ist (Sie müssen die sysread und pflegen Sie Ihren eigenen Puffer). So können Sie die Aktivität des Bildschirmschoners überwachen und etwas unternehmen, wenn er 15 Minuten lang eingeschaltet war.

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

Ich habe das nicht getestet, aber vielleicht reicht es für Sie aus, damit es funktioniert.

P.S. Es funktioniert nicht unter Windows, wo <code>select</code> funktioniert nur bei Steckdosen.

3voto

mob Punkte 113680

Die Lösungen von Sinan und nandhp sind für diese Aufgabe geeignet. threads y select sind mächtige Werkzeuge im Arsenal des Perl-Programmierers, aber ich würde zögern, sie jemandem für sein "allererstes Perl (sic) Programm" zu empfehlen. Also schlage ich einen anderen Ansatz vor.

Um die Aussage dieses Problems zu vereinfachen, wollen wir etwas tun (einen Befehl zum Beenden von Prozessen auf einem entfernten Server auslösen), wenn etwas anderes passiert (der Bildschirmschoner ist seit 15 Minuten aktiv).

 use strict;
 use warnings;

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

En do_something Teil ist ganz einfach:

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

Für die something_happens Teil des Programms ist, würde ich vorschlagen, die dbus-monitor Ausgabe in eine Datei in einem Hintergrundprozess und Lesen aus der Datei, wenn Sie den Status des Bildschirmschoners wissen wollen. Die Website dbus-monitor Programm erzeugt die Ausgabe ziemlich langsam, und das Lesen von einem Perl-Filehandle wird dazu neigen, zu blockieren (es sei denn, Sie lernen und benutzen select ).

Ich werde die dbus-monitor Befehl ein wenig. Dieser Befehl gibt jedes Mal einen Zeitstempel aus, wenn sich der Zustand des Bildschirmschoners ändert:

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

und wir starten unser Programm, indem wir es ausführen:

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

Um nun zu sehen, ob und wie lange der Bildschirmschoner aktiv ist, analysieren wir /tmp/screensavermonitor hin und wieder.

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

1voto

Dave Sherohman Punkte 44017

Wie Mob schon sagte, Fäden und select überkomplizieren die Sache ein wenig. Hier ist also etwas Schönes und Einfaches mit Begriff::ReadKey die es Ihnen ermöglicht, das zu tun, was Sie von vornherein gewünscht haben: Warten, bis eine Taste gedrückt wird, aber Timeout, wenn innerhalb von 15 Minuten keine Taste gedrückt wird.

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

(Der Code ist syntaktisch korrekt, wurde aber noch nicht ausgeführt und ist daher nicht vollständig getestet. Möglicherweise möchten Sie zusätzlich zu "cbreak" auch den Lesemodus "noecho" einstellen, um zu verhindern, dass der Tastendruck, der Megadeath deaktiviert, auf dem Bildschirm erscheint).

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X