Perl-Snapshot: Zurück nach damals

Aufmacher News

Perl-Snapshot: Zurück nach damals

Mike Schilli
13.07.2000

Es muss nicht immer eine aufwendige X11-basierte Benutzeroberfläche sein,eine textbasierte Applikations tut's oft auch. In der heutigen Perl-Folge wirddas klassische Curses-Paket vorgestellt, mit dem sich solche Programme leicht erstellen lassen.

Das Millennium rückt heran, Zeit ein wenig sentimental zu werden. Mit "Damals, als ich mit Konrad Zuse im Cafe saß", pflegte ein Informatik-Dozent an der Uni immer seine Geschichten einzuleiten. Heute sage ich mal: Damals, als Computer noch keine Mäusehatten, und ich um die Gunst der langbärtigen Unix-Gurus im Rechnerraumbuhlen mußte, um Zugang zu den heiligen Maschinen zu erlangen, damals, eswar wohl Ende der Achtziger, stieß ich auf Curses, ein Programm-Paket, mit dem man unter C einfache graphische Oberflächenauf die Text-Terminals zaubern konnte: Kleine Textformulare, und auch netteMenüs, aus denen man Einträge auswählen konnte, indem man mit denCursortasten der Tastatur auf- und abfuhr. Nachdem ich auch damals schonkeine Apple-Computer mochte und das X-Window-System und natürlich MicrosoftWindows noch unbekannt waren, begann ich begeistert, einigen meinerC-Programme eine Oberfläche zu verpassen.

Auch heute noch, wo Window-Systeme kaum noch wegzudenken sind, hat Curses seinen Platz: Wo immer eine vollständige Window-Oberfläche zuspeicheraufwendig ist, zu lange zum hochfahren braucht, oder die graphischeDatenübermittlung wegen langsamer Netzverbindung ätzend lange dauert,leistet Curses gute Dienste. Auch für Perl gibt es natürlich ein Curses-Paket auf dem CPAN, und darum soll's heute gehen.Eine ausführliche Behandlung der Curses-Bibliothek (allerdings aus Sichtder C-Schnittstelle) findet sich in [1], einem der erstenO'Reilly-Nutshell-Bücher. 1986 erschienen, ist es fünf Jahre älter als dieerste Auflage von ``Programmieren mit Perl'' -- das lila Camel, daskostbarste Devotionalium in meinem Bücherregal.

Curses - what is it good for?

Curses spricht Unix-Terminals an, malt Zeichen an bestimmten Stellen einesText-Fensters und nimmt Eingaben von der Tastatur entgegen. Curses löst sich aber bewußt von den Eigenheiten tausenderlei verschiedenerUnix-Terminaltypen. Es bietet eine abstrakte Schnittstelle an, die aufallen Maschinen gleich aussieht, egal was das darunterliegende Terminaltreibt. Unter Linux stützt es sich auf die in /etc/termcap definierten Strukturen, die für jeden Terminal-Typ festlegen, welcheKontrollsequenzen (z.B. CTRL-A) welche Kommandos auslösen (z.B. Screen löschen).

Curses optimiert Ausgaben an ein Terminal dadurch, dass es intern mit logischenScreens arbeitet: Zeichenbefehle in Curses (wie z. B. addstr) schreiben immer in logische Fenster (z. B. das Hauptfenster stdscreen). Wenn die Applikation dann mit dem refresh-Befehl das Kommando gibt, die Änderungen tatsächlich anzuzeigen, zeichnet Curses nur die Bereiche des Terminals nach, die sich seit dem letzten refresh geändert haben. Da es sehr viel Zeit in Anspruch nähme, das Terminal selbst nach seinem momentanen Zustand zu befragen, um den Abgleich durchzuführen, hält sich Curses ein weiteres logisches Fenster, curscreen, vor, in dem es festhält, wie das Terminal seiner Vorstellung nach gerade aussieht. Der refresh-Befehl stellt die Unterschiede zwischen stdscreen und curscreen fest, sendet notwendige Änderungen ans Terminal und zieht diese gleichzeitig in curscreen nach, damit curscreenund das tatsächliche Terminal sich in Einklang befinden.

Um mit Curses loszulegen, muß das Perl-Skript zunächst die Initialisierung mit initscr einleiten. endwin beendet Curses und sollte auf jeden Fall vor Abschluß des Skripts aufgerufen werden. Unterbleibt dies, befindet sich das Terminal in einem traurigen Zustand und nimmt unter Umständen nicht einmal mehr Eingaben an. Das einzige was dann im allgemeinen noch bleibt, ist, unter Umständen blind das Kommando tset mit einem anschließenden CTRL-J einzugeben und abzuwarten, bis das Terminal sich wieder beruhigt.endwin vermeidet derartigen Unbill. Um ein sauberes Ende auch bei Programmabbruch mit CTRL-C oder ähnlichem zu gewährleisten, finden Signal-Handler Anwendung, die vor dem eigentlichen Ende noch schnell ein endwin absetzen.

Curses arbeitet normalerweise mit dem Hauptfenster stdscr, das in der Steinzeit der Datenverarbeitung den gesamten Bildschirmausfüllte und im Linux-Zeitalter ein X-Fenster belegt, oder falls X nichtläuft, eine virtuelle Konsole. Für ausgefuchstere Oberflächen kann man außerdem Sub-Windows innerhalb des Hauptfensters definieren.

Die Curses-Wirbelwind-Tour

Für die Jüngeren unter Euch, habe ich einen Curses-Schnellkurs vorbereitet, bitte anschnallen: initscr inititalisiert Curses und muß vor irgendwelchen anderen Funktionen abgesetzt werden. endwin beendet Curses.move($y, $x) bewegt den Terminal-Cursor auf die angegebene Position, $y ist die Zeile, $x die Spalte. addstr($string) schreibt einen String, an der aktuellen Cursorposition beginnend. standout schaltet in einen Modus, in dem alle nachfolgend ausgegebenen Zeichenketten hervorgehoben dargestellt werden (je nach Terminal farbig oder invers oder beides). standend beendet den Modus, den standout einleitete. keypad($flag) fasst, mit einem wahren Wert für $flag, die Sequenzen, die beim Drücken einer Sondertaste beim Terminal hereinpurzeln, zu einem einzigen Code zusammen. getch wartet darauf, dass eine Taste gedrückt wird. Es blockiert normalerweise solange, bis etwas passiert und liefert dann den Wert der gedrückten Tastezurück. nodelay($flag) veranlaßt getch dazu, nicht zu blockieren, falls $flag auf einen wahren Wert gesetzt wurde. noecho unterdrückt das Schreiben der Werte gedrückter Tasten auf dem Bildschirm. echo schaltet das Terminal-Echo wieder ein. Und schließlich der wichtigste Befehl: refresh, der die durchgeführten Änderungen auf dem Bildschirm erscheinen läßt,unterbleibt er, passiert nichts.

Der billige Monitor

Dieses Wissen sollte ausreichen, um ein kleines Monitorprogramm zuschreiben, das laufend den Zustand der Festplatten eines Rechners anzeigtund außerdem überprüft, ob eine Reihe ausgewählter Webserver laufen oder den Geist aufgegeben haben. Das schöne an Curses ist, dass solche Utilities auch dann sehr schön zu bedienen sind, wenn mansich über ein Modem mit telnet irgendwo am Ende der Welt einloggt.

Listing 1: ProgressBar.pm


 1 ##################################################
 2 # Progress Bar - 1999, mschilli@perlmeister.com
 3 ##################################################
 4 package ProgressBar;
 5 
 6 ##################################################
 7 sub new {
 8 ##################################################
 9     my ($class, $maxlen) = @_;
10     my $self  = { maxlen => $maxlen};
11     bless $self, $class;
12     return $self;
13 }
14 
15 ##################################################
16 sub status {
17 ##################################################
18     my ($self, $percent) = @_;
19 
20     my $barlen = $percent/100.0 * $self->{maxlen};
21     sprintf "[%-$self->{maxlen}s] (%d%%)  ", 
22             "=" x $barlen, $percent;
23 }
24 
25 1;

In Listing 1 (ProgressBar.pm) ist ein kleines Hilfsmodul definiert, das langweilige Prozentzahlen mit den bescheidenen Mitteln eines Text-Terminals grafisch darstellt. Wenn man mit ProgressBar->new($maxlen) ein neues Objekt erstellt, dessen Fortschrittsanzeige maximal $maxlen Zeichen lang wird, liefert die status-Methode anschliessend zu übermittelten Prozentzahlen passendeProgessanzeigen, aus 71% wird so flugs der String

[=======   ] (71%)

ProgressBar.pm zeigt die Implementierung des Konstruktorsnew, der nur den Wert des hereingegebenen Parameters in der Instanzvariablen maxlen abspeichert. Die status-Methode hingegen nimmt einen Prozentwert entgegen, kalkuliert, wie lange dann der dargestellte Balken wird und nutzt die sprintf-Funktion, um einen mit "=" x $barlen erzeugten Balken linksbündig in ein $maxlen großes Feld einzupassen. Die status-Methode erzeugt die hierzu notwendige Formatieranweisung im Format"%-10s" dynamisch.

Abb.1: Der Monitor in Aktion

Der eigentliche Monitor steht in Listing 2 (monitor.pl), dessen Ausgabe Abbildung 1 zeigt. Es zieht das Curses-Modul, unser gerade geschriebenes ProgressBar und schließlich LWP::Simple, das später beim Überprüfen von Webseiten helfen wird. Nach der Curses-Initialisierung in den Zeilen 10 bis 14 definiert Zeile 16 einen Signal-Handler: Die Funktion quit, die ab Zeile 119 definiert ist und fatalen Fehlern oder erzwungenem Programmabbruch schnell die übergebene Fehlermeldunng ausgibt, Curses abräumt und das Programm mit exit beendet.Die Zeilen 18 und 19 schreiben die Überschrift, ab Zeile 21 steht die while-Schleife, die alle 10 Minuten einen Durchgang steuert, und solange im Kreis läuft, bis der Benutzer die Tasten 'q' oder 'BACKSPACE' drückt, um den Monitor zu beenden.

Listing 2: monitor.pl


 1 #!/usr/bin/perl -w
 2 ##################################################
 3 # monitor.pl - 1999, mschilli@perlmeister.com
 4 ##################################################
 5 
 6 use Curses;
 7 use ProgressBar;    # Unser eigenes ProgressBar.pm
 8 use LWP::Simple;
 9 
10 initscr;            # Curses initialisieren
11 keypad(1);          # Funny keys mappen
12 clear;              # Bildschirm löschen
13 noecho();           # Kein Tastenecho
14 nodelay(1);         # getch() soll nicht blocken
15 $SIG{__DIE__} =
16 $SIG{INT} = &quit; # Aktion auf CTRL-C 
17 
18 move(0,0);
19 addstr("My Cute Little Monitor v1.0");
20 
21 while(1) {
22 
23         # Eventuell mehrere Tasten abfragen
24     while ((my $key = getch()) ne ERR) {
25         quit() if ($key eq 'q' || 
26                    $key eq KEY_BACKSPACE);
27     }
28 
29         # Abfragen starten
30     print_update(1);
31     plot_df("/", 2, 0);
32     plot_df("/dos", 3, 0);
33     plot_webhost("http://www.yahoo.com", 4, 0);
34     plot_webhost("http://www.br-online.de", 5, 0);
35     plot_webhost("http://www.amazon.com", 6, 0);
36     print_update(0);
37 
38         # 10 Minuten auf Tastendruck warten
39     $bitmap = '';
40     vec($bitmap, fileno(STDIN), 1) = 1;
41     select($bitmap, undef, undef, 600);
42 }
43 
44 endwin;
45 
46 ##################################################
47 # Auslastung von Plattenpartition $partition auf
48 # Bildschirmkoordinaten $y/$x anzeigen
49 ##################################################
50 sub plot_df {
51     my ($partition, $y, $x) = @_;
52 
53     my $progress = ProgressBar->new(10);
54 
55     move($y, $x);
56     addstr($partition);
57     move($y, $x+30);
58     addstr($progress->status(getdf($partition))); 
59     refresh;
60 }
61 
62 ##################################################
63 # Host $host pingen und auf Terminalkoordinaten
64 # $y/$x anzeigen
65 ##################################################
66 sub plot_webhost {
67     my ($url, $y, $x) = @_;
68 
69     my ($dispurl) = ($url =~ m#//([^/]+)#);
70 
71     move($y, $x);
72     addstr($dispurl);
73     refresh;
74     move($y, $x+30);
75     addstr(pingurl($url) ? "Up  " : "Down");
76     refresh;
77 }
78     
79 ##################################################
80 # Plattenauslasten für Partition $part ermitteln
81 ##################################################
82 sub getdf {
83     my $part = shift;
84     my $percentage;
85 
86     open PIPE, "/bin/df -k $part |" or 
87          quit("Cannot run /bin/df");
88     while(<PIPE>) {
89          ($percentage) = /(d+)%/;
90     }
91     close PIPE or quit("/bin/df failed");
92     $percentage;
93 }
94 
95 ##################################################
96 # Bei $host anklopfen, 0=host down, 1=host up
97 ##################################################
98 sub pingurl {
99     my $url = shift;
100 
101     return(defined(get($url)));
102 }
103 
104 ##################################################
105 sub print_update {
106 ##################################################
107     my $on = shift;
108 
109     $msg = "Updating ...";
110 
111     standout() if $on;
112     $msg = " " x length($msg) unless $on;
113     addstr($LINES-1, $COLS - length($msg), $msg);
114     standend() if $on;
115     refresh();
116 }
117 
118 ##################################################
119 sub quit { 
120 ##################################################
121     print "@_
";
122     endwin(); 
123     exit(0); 
124 }

Warten auf Ereignisse

Die Funktion getch() wartet, wie gesagt, bis der Anwender eine Taste drückt, kehrt daraufhin zurück und liefert den Wert der Taste. Ein Programm, das aber inregelmäßigen Zeitabständen Ausgaben auf dem Bildschirm aktualisiert und nurfür den Fall, dass der Anwender irgendwann auf eine Taste drückt, etwasanderes tut (z. B. das Programm beenden), kann nicht ständig in getch() herumhängen. Der Aufruf von nodelay(1) bewirkt, dass ein nachfolgend aufgerufenes getch() nicht blockiert, sondern entweder den Wert einer gedrückten Taste zurückliefert oder einen Fehler, falls keine Eingabe vorlag.

getch() liefert eine Zahl ungleich ERR (ein in Curses definiertes Macro) zurück, falls eine Taste gedrückt wurde. Es wäre abereine Verschwendung von Rechenpower, aktiv immer wieder getch()aufzurufen, bis etwas passiert. Abhilfe schafft hier select, der Unix-System-Call, der auf Ereignisse wartet. Perl bietet ja zwei select-Funktionen, die sich in der Zahl der übergebenen Parameter unterscheiden: Während die einparametrige die Ausgabe der print-Funktionen auf einen File-Deskriptor umleitet, ist die zweite,vierparametrige, dazu da, eine Schnittstelle zum Unix-System-Aufruf select anzubieten.

Aus Unix-Tradition erwartet der select-Aufruf ein etwas ungewöhnliches Parameterformat. select wartet auf Ereignisse, die in dreierlei Filedeskriptoren auftreten: Eine erste Reihe von Filedeskriptoren wird daraufhin überwacht, ob dort Daten zum Lesen anliegen, eine zweite Reihe schlägt Alarm, falls es OK ist,dorthin zu schreiben und eine dritte Reihe wird daraufhin untersucht, ob dort irgendwelche Fehler (Exceptions) passierten. Außerdem läßt sich ein Timeout angeben, nachdem die Funktion zurückkehrt, auch wenn nichts passiert ist.

Die Deskriptorensammlungen werden als Bit-Arrays an select übergeben. Um nur den Deskriptor der Standardeingabe zu überwachen, muss zunächst die Nummer des Filedeskriptors aus den in Perl üblichen File-Handlesermittelt werden: Die Funktion fileno(STDIN) liefert die Filedeskriptornummer der Standardeingabe zurück (im allgemeinen 0 ) und das entsprechende Bit im Vektor, den select versteht, setzt die selten verwendete Perl-Funktion vec:

$in = "";
vec($in, fileno(STDIN), 1) = 1;

Mit dem in $in gesetzten Wert wartet nun

select($in, undef, undef, 10);

darauf, dass in der Standardeingabe etwas passiert. Nach den eingestellten10 Sekunden Timeout wird abgebrochen. Um auch Sondertasten zu erkennen,hilft ein Vorab-Aufruf von keypad(1). Während diese Funny Keys normalerweise mehrere Werte zurückliefern, fasst Curses nach keypad(1) diese Werte zu einer Konstante zusammen -- die wichtigsten als Macroverfügbaren sind die Cursortasten KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_BACKSPACE und KEY_ENTER.

monitor.pl implementiert diese Logik in den Zeilen 24 bis 27 und 39 bis 41. Dieeinzelnen Aktionen, die der Monitor ausführt, finden in den Zeilen 30 bis36 statt. Zunächst sorgt die Funktion print_update dafür, dass rechts unten im Fenster hell erleuchtet der Text ``Updating ...'' zu lesen ist. print_update, die ab Zeile 105 definiert ist, nimmt einen Parameter entgegen, der, jenachdem ob er einen wahren oder falschen Wert aufweist, den Text erscheinenoder verschwinden läßt. Zeile 111 schaltet in den Modus, der den Textfarbig hervorhebt, Zeile 113 gibt ihn unter Zuhilfenahme der von Curses exportierten Konstanten $COLS (Anzahl der Spalten des Terminals) und $LINES (Anzahl der Zeilen) rechts unten im Fenster aus. Zeile 114 schaltet wiederzurück in den normalen Textmodus, der anschließende refresh-Befehl macht den Text auf dem Bildschirm sichtbar.

Weiter in Zeile 31: Die Funktion plot_df ermittelt die Auslastung einer angegebenen Festplattenpartition und gibt eine grafische Darstellung an den angegebenen Bildschirmpositionen aus. plot_df ist ab Zeile 50 definiert, ruft die getdf-Funktion für die angegebene Partition auf, verwandelt den gewonnenenProzentwert mit dem ProgressBar-Modul in die Textgrafik und gibt sie zusammen mit dem Partitionsnamen auf dem Terminal aus. getdf steht ab Zeile 82 und selbst zapft nur das df-Kommando an, sucht nach der angegebenen Partition und dem %-Zeichen und gibt den gefundenen Zahlenwert zurück.

Ähnlich arbeitet die Funktion plot_webhost, die ab Zeile 66 definiert ist, dort den Namen des Rechners aus demübergebenen URL extrahiert und zusammen mit dem Ergebnis der Funktion pingurl anzeigt. pingurl ab Zeile 98 nutzt die get-Funktion aus LWP::Simple, um das gewünschte Web-Dokument zu holen und liefert einen wahren Wert zurück, falls die Seite erfolgreich eingeholt werden konnte.

Installation

Wer Curses noch nicht auf dem heimischen Rechner verfügbar hat, kann es und das außerdem benötigte LWP::Simple mit der komfortablen CPAN-Shell folgendermaßen vom CPAN holen und installieren:

perl -MCPAN -eshell
cpan> install Curses
cpan> install LWP::Simple

Das neue Modul ProgressBar aus Listing ProgressBar.pm muß irgendwo stehen, wo monitor.pl es auch findet, am einfachsten im gleichen Verzeichnis, in dem auch monitor.pl haust.

Die Zeilen 31 bis 35 müssen anschließend noch an die lokalen Gegebenheitenangepasst werden, die Namen der zu überwachenden Partitionen und die URLsvon Webseiten, die es zu überwachen gilt, müssen hier hinein -- und wernoch weitere Ideen hat, kann hier seiner Fantasie freien Lauf lassen. Prozesse, die nicht runterfallen dürfen mit ps überwachen? Anzahl der aktiven Benutzer mit who ausfiltern und anzeigen? The sky's the limit, wie immer ... a guat's nei's Jahrdausn'd, Leidln!

Infos


[1] Programming with Curses, John Strang, O'Reilly, 1986
[2] Tom Christiansen and Nathan Torkington, The Perl Cookbook, S.532, O'Reilly, 1998

Der Autor


Michael Schilli arbeitet als Web-Engineer für AOL/Netscape in Mountain View, Kalifornien.Er ist Autor des 1998 bei Addison-Wesley erschienenen (und 1999 für den englischsprachigen Markt als Perl Power herausgekommenen) Buches "GoTo Perl 5" und unter michael@perlmeister.com oderhttp://perlmeister.com zu erreichen.

Copyright © 1999 Linux-Magazin Verlag

Ähnliche Artikel

Kommentare

Aktuelle Fragen

Lieber Linux oder Windows- Betriebssystem?
Sina Kaul, 13.10.2017 16:17, 2 Antworten
Hallo, bis jetzt hatte ich immer nur mit
IT-Kurse
Alice Trader, 26.09.2017 11:35, 2 Antworten
Hallo liebe Community, ich brauche Hilfe und bin sehr verzweifelt. Ih bin noch sehr neu in eure...
Backup mit KUP unter Suse 42.3
Horst Schwarz, 24.09.2017 13:16, 3 Antworten
Ich möchte auch wieder unter Suse 42.3 mit Kup meine Backup durchführen. Eine Installationsmöglic...
kein foto, etc. upload möglich, wo liegt mein fehler?
kerstin brums, 17.09.2017 22:08, 5 Antworten
moin, zum erstellen einer einfachen wordpress website kann ich keine fotos uploaden. vom rechne...
Arch Linux Netzwerkkonfigurationen
Franziska Schley, 15.09.2017 18:04, 0 Antworten
Moin liebe Linux community, ich habe momentan Probleme mit der Einstellung des Lan/Wlan in Arc...