Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

 << zurück
Shell-Programmierung von Jürgen Wolf
Einführung, Praxis, Referenz
Buch: Shell-Programmierung

Shell-Programmierung
782 S., mit CD, 44,90 Euro
Rheinwerk Computing
ISBN 3-89842-683-1
gp Kapitel 5 Terminal-Ein- und Ausgabe
  gp 5.1 Von Terminals zu Pseudo-Terminals
  gp 5.2 Ausgabe
    gp 5.2.1 Der echo-Befehl
    gp 5.2.2 print (Korn-Shell only)
    gp 5.2.3 Der Befehl printf
    gp 5.2.4 Der Befehl tput – Terminalsteuerung
  gp 5.3 Eingabe
    gp 5.3.1 Der Befehl read
    gp 5.3.2 (Zeilenweise) Lesen einer Datei mit read
    gp 5.3.3 Zeilenweise mit einer Pipe aus einem Kommando lesen (read)
    gp 5.3.4 Here-Dokumente (Inline-Eingabeumleitung)
    gp 5.3.5 Here-Dokumente mit read verwenden
    gp 5.3.6 Die Variable IFS
    gp 5.3.7 Arrays einlesen mit read (Bash und Korn-Shell only)
    gp 5.3.8 Shell-abhängige Anmerkungen zu read
    gp 5.3.9 Einzelnes Zeichen abfragen
    gp 5.3.10 Einzelne Zeichen mit Escape-Sequenzen abfragen
    gp 5.3.11 Passworteingabe
  gp 5.4 Umlenken mit dem Befehl exec
  gp 5.5 Filedeskriptoren
    gp 5.5.1 Einen neuen Filedeskriptor verwenden
    gp 5.5.2 Die Umlenkung <>
  gp 5.6 Named Pipes
  gp 5.7 Menüs mit select (Bash und Korn-Shell only)
  gp 5.8 dialog und Xdialog
    gp 5.8.1 Entscheidungsfrage --yesno
    gp 5.8.2 Nachrichtenbox mit Bestätigung --msgbox
    gp 5.8.3 Hinweisfenster ohne Bestätigung --infobox
    gp 5.8.4 Text-Eingabezeile --inputbox
    gp 5.8.5 Ein einfacher Dateibetrachter --textbox
    gp 5.8.6 Ein Menü --menu
    gp 5.8.7 Auswahlliste zum Ankreuzen --checklist
    gp 5.8.8 Radiobuttons zum Auswählen --radiolist
    gp 5.8.9 Fortschrittszustand anzeigen --gauge
    gp 5.8.10 Verändern von Aussehen und Ausgabe
    gp 5.8.11 Kleines Beispiel
    gp 5.8.12 Zusammenfassung
  gp 5.9 gnuplot – Visualisierung von Messdaten
    gp 5.9.1 Wozu wird gnuplot eingesetzt?
    gp 5.9.2 gnuplot starten
    gp 5.9.3 Das Kommando zum Plotten
    gp 5.9.4 Variablen und Parameter für gnuplot
    gp 5.9.5 Ausgabe von gnuplot umleiten
    gp 5.9.6 Variablen und eigene Funktionen definieren
    gp 5.9.7 Interpretation von Daten aus einer Datei
    gp 5.9.8 Alles bitte nochmals zeichnen (oder besser speichern und laden)
    gp 5.9.9 gnuplot aus einem Shellscript heraus starten (der Batch-Betrieb)
    gp 5.9.10 Plot-Styles und andere Ausgaben festlegen
    gp 5.9.11 Tricks für die Achsen
    gp 5.9.12 Die dritte Dimension
    gp 5.9.13 Zusammenfassung


Rheinwerk Computing

5.3 Eingabdowntop

Neben der Ausgabe werden Sie relativ häufig auch die Benutzereingaben benötigen, um ein Script entsprechend zu steuern. Darauf soll jetzt näher eingegangen werden.


Rheinwerk Computing

5.3.1 Der Befehl read  downtop

Mit dem Befehl read können Sie die (Standard-)Eingabe von der Tastatur lesen und in einer Variablen abspeichern. Selbstverständlich wird bei einem Aufruf von read die Programmausführung angehalten, bis die Eingabe erfolgt ist und (ENTER) betätigt wurde. Die Syntax für read:

# Eingabe von Tastatur befindet sich in der variable
read variable

Beispielsweise:

you@host > read myname
Jürgen
you@host > echo $myname
Jürgen
you@host > read myname
Jürgen Wolf
you@host > echo $myname
Jürgen Wolf

Hier sehen Sie auch gleich, dass read die komplette Eingabe bis zum (ENTER) einliest, also inklusive Leerzeichen (Tab-Zeichen werden durch ein Leerzeichen ersetzt). Wollen Sie aber anstatt wie hier (Vorname, Nachname) beide Angaben in einer separaten Variablen speichern, müssen Sie read folgendermaßen verwenden:

read variable1 variable2 ... variable_n

read liest hierbei für Sie die Zeile ein und trennt die einzelnen Worte anhand des Trennzeichens in der Variablen IFS auf. Wenn mehr Wörter eingegeben wurden, als Variablen vorhanden sind, bekommt die letzte Variable den Rest einer Zeile zugewiesen, zum Beispiel:

you@host > read vorname nachname
Jürgen Wolf
you@host > echo $vorname
Jürgen
you@host > echo $nachname
Wolf
you@host > read vorname nachname
Jürgen Wolf und noch mehr Text
you@host > echo $vorname
Jürgen
you@host > echo $nachname
Wolf und noch mehr Text

Sie haben außerdem die Möglichkeit, read mit einer Option zu verwenden:

read -option variable

Hierzu stehen Ihnen sehr interessante Optionen zur Verfügung, die in Tabelle 5.9 aufgelistet sind.


Tabelle 5.9   Optionen für das Kommando read

Option Bedeutung
–n anzahl Hier muss die Eingabe nicht zwangsläufig mit (ENTER) abgeschlossen werden. Wenn die anzahl Zeichen erreicht wurde, wird auch ohne (ENTER) in die Variable geschrieben.
–s (s = silent) Hierbei ist die Eingabe am Bildschirm nicht sichtbar, wie dies etwa bei einer Passworteingabe der Fall ist.
–t sekunden Hiermit können Sie ein Timeout für die Eingabe vorgeben. Wenn read binnen Anzahl sekunden keine Eingabe erhält, wird das Programm fortgesetzt. Der Rückgabewert bei einer Eingabe ist 0. Wurde binnen vorgegebener Zeit keine Eingabe vorgenommen, wird 1 zurückgegeben. Hierfür können Sie zur Überprüfung die Variable $? verwenden.

Auf den folgenden Seiten werden Sie verblüfft sein, wie vielseitig das Kommando read wirklich ist. Für die Eingabe (nicht nur) von der Tastatur ist read somit fast das Nonplusultra.


Rheinwerk Computing

5.3.2 (Zeilenweise) Lesen einer Datei mit read  downtop

Eine wirklich bemerkenswerte Funktion erfüllt read mit dem zeilenweisen Auslesen von Dateien, beispielsweise:

you@host > cat zitat.txt
Des Ruhmes Würdigkeit verliert an Wert,
wenn der Gepriesene selbst mit Lob sich ehrt.
you@host > read variable < zitat.txt
you@host > echo $variable
Des Ruhmes Würdigkeit verliert an Wert,

Damit hierbei allerdings auch wirklich alle Zeilen ausgelesen werden (Sie ahnen es schon), ist eine Schleife erforderlich. Trotzdem sieht die Verwendung von read garantiert anders aus, als Sie jetzt sicher vermuten:

while read var < datei
do
 ...
done

Würden Sie read auf diese Weise verwenden, so wäre es wieder nur eine Zeile, die gelesen wird. Hier müssen Sie die Datei in das komplette while-Konstrukt umleiten:

while read var
do
 ...
done < datei

Ohne großen Aufwand können Sie so zeilenweise eine Datei einlesen:

# Demonstriert den Befehl read zum zeilenweisen Lesen einer Datei
# Name : areadline
if [ $# -lt 1 ]
then
   echo "usage: $0 datei_zum_lesen"
   exit 1
fi
# Argument $1 soll zeilenweise eingelesen werden
while read variable
do
   echo $variable
done < $1

Das Script bei der Ausführung:

you@host > ./areadline zitat.txt
Des Ruhmes Würdigkeit verliert an Wert,
wenn der Gepriesene selbst mit Lob sich ehrt.

Diese Methode wird sehr häufig verwendet, um aus größeren Dateien einzelne Einträge herauszufiltern. Des Weiteren ist sie eine tolle Möglichkeit, um die Variable IFS in Schleifen zu überlisten. Damit können Sie Schleifen nicht mehr einmal pro Wort, sondern einmal pro Zeile ausführen lassen. Es ist der Hack schlechthin, wenn etwa Dateien, deren Namen ein Leerzeichen enthalten, verarbeitet werden sollen.


Rheinwerk Computing

5.3.3 Zeilenweise mit einer Pipe aus einem Kommando lesen (read)  downtop

Wenn das Umlenkungszeichen (wie im Abschnitt zuvor gesehen) in einer Schleife mit read funktioniert, um zeilenweise aus einer Datei zu lesen, sollte Gleiches auch mit einer Pipe vor einer Schleife und einem Kommando gelingen. Sie schieben so quasi die Standardausgabe eines Kommandos in die Standardeingabe der Schleife – immer vorausgesetzt natürlich, hier wird read verwendet, das ja auch etwas von der Standardeingabe erwartet. Und auch hier funktioniert das Prinzip so lange, bis read keine Zeile mehr lesen kann und somit 1 zurückgibt, was gleichzeitig das Ende der Schleife bedeutet. Hier die Syntax eines solchen Konstrukts:

kommando | while read line
do
   # Variable line bearbeiten
done

Um beim Beispiel »areadline« vom Abschnitt zuvor zu bleiben, sieht das Script mit einer Pipe nun wie folgt aus:

# Demonstriert den Befehl read zum zeilenweisen Lesen einer Datei
# Name : areadline2
if [ $# -lt 1 ]
then
   echo "usage: $0 datei_zum_lesen"
   exit 1
fi
# Argument $1 soll zeilenweise eingelesen werden
cat $1 | while read variable
do
   echo $variable
done

Ausgeführt macht das Beispiel dasselbe, wie schon das Script »areadline« zuvor. Beachten Sie allerdings, dass nach jedem erneuten Schleifendurchlauf die Pipe geschlossen und die Variable somit verworfen wird.


Rheinwerk Computing

5.3.4 Here-Dokumente (Inline-Eingabeumleitung)  downtop

Bisher wurde noch nicht auf das Umleitungszeichen << eingegangen. Sie kennen bereits das Umleitungszeichen <, mit dem Sie die Standardeingabe umlenken können. Das Umlenkungssymbol >> der Ausgabe hat ja die Ausgabedateien ans Ende einer Datei gehängt. Bei der Standardeingabe macht so etwas keinen Sinn. Mit einfachen Worten ist es kompliziert, diese Umlenkung zu beschreiben, daher hier zunächst einmal die Syntax:

kommando <<TEXT_MARKE
...
TEXT_MARKE

Das Kommando nutzt in diesem Fall durch das doppelte Umlenkungszeichen alle nachfolgenden Zeilen für die Standardeingabe. Dies geschieht so lange, bis es auf das Wort trifft, welches hinter dem Umlenkungszeichen << folgt (in der Syntaxbeschreibung ist dies das Wort TEXT_MARKE). Beachten Sie außerdem, dass beide Textmarken absolut identisch sein müssen.

Ein Beispiel:

# Demonstriert die Verwendung von Here-Dokumenten
# Name : ahere1
cat <<TEXT_MARKE
Heute ist `date`,
Sie befinden sich im Verzeichnis `pwd`. Ihr
aktuelles Terminal ist `echo -n $TERM` und
Ihr Heimverzeichnis befindet sich in $HOME.
TEXT_MARKE

Das Script bei der Ausführung:

you@host > ./ahere1
Heute ist Fr Mär  4 00:36:37 CET 2005,
Sie befinden sich im Verzeichnis /home/tot. Ihr
aktuelles Terminal ist xterm und
Ihr Heimverzeichnis befindet sich in /home/you.

In diesem Beispiel liest das Kommando cat so lange die nachfolgenden Zeilen, bis es auf das Wort TEXT_MARKE stößt. Hierbei können auch Kommando-Substitutionen (was durchaus ein anderes Shellscript sein darf) und Variablen innerhalb des Textes verwendet werden. Die Shell kümmert sich um entsprechende Ersetzung. Der Vorteil von »Here-Dokumenten« ist, dass Sie einen Text direkt an Befehle weiterleiten können, ohne diesen vorher in einer Datei unterbringen zu müssen. Gerade bei der Ausgabe von etwas umfangreicheren Texten oder Fehlermeldungen fällt einem die Ausgabe hier wesentlich leichter.

Wollen Sie allerdings nicht, dass eine Kommando-Substitution oder eine Variable von der Shell interpretiert wird, müssen Sie nur zwischen dem Umlenkungszeichen << und der Textmarke einen Backslash setzen:

# Demonstriert die Verwendung von Here-Dokumenten
# Name : ahere2
cat <<\TEXT_MARKE
Heute ist `date`,
Sie befinden sich im Verzeichnis `pwd`. Ihr
aktuelles Terminal ist `echo -n $TERM` und
Ihr Heimverzeichnis befindet sich in $HOME.
TEXT_MARKE

Dann sieht die Ausgabe folgendermaßen aus:

you@host > ./ahere2
Heute ist `date`,
Sie befinden sich im Verzeichnis `pwd`. Ihr
aktuelles Terminal ist `echo -n $TERM` und
Ihr Heimverzeichnis befindet sich in $HOME.

Neben der Möglichkeit mit einem Backslash zwischen dem Umlenkungszeichen << und der Textmarke, existiert auch die Variante, die Textmarke zwischen Single Quotes zu stellen:

kommando <<'MARKE'
...
MARKE

Befinden sich außerdem im Text führende Leer- oder Tabulatorzeichen, können Sie diese mit einem Minuszeichen zwischen dem Umlenkungszeichen und der Textmarke entfernen (<<–MARKE ).

Nichts hält Sie übrigens davon ab, die Standardeingabe mittels << an eine Variable zu übergeben:

# Demonstriert die Verwendung von Here-Dokumenten
# Name : ahere3
count=`cat <<TEXT_MARKE
\`ls -l | wc -l\`
TEXT_MARKE`
echo "Im Verzeichnis $HOME befinden sich $count Dateien"

Das Script bei der Ausführung:

you@host > ./ahere3
Im Verzeichnis /home/tot befinden sich 40 Dateien

Damit in der Variable »count« nicht die Textfolge ls l | wc –l steht, sondern auch wirklich eine Kommando-Substitution durchgeführt wird, müssen Sie die Kommandos durch einen Backslash schützen, weil hier bereits die Ausgabe der »Textmarke« als Kommando-Substitution verwendet wird.

Ich persönlich verwende diese Inline-Eingabeumleitung gern in Verbindung mit Fließkommaberechnungen und dem Kommando bc (siehe auch Abschnitt 2.2.3). Verwenden Sie hierbei zusätzlich noch eine Kommando-Substitution, können Sie das Ergebnis der Berechnung in einer Variablen speichern, wie Sie dies von expr her kennen. Außerdem können Sie mit der Verwendung der mathematischen Bibliothek (anzugeben mit der Option –l) noch weitere Winkelfunktionen mit z. B. Sinus (s()) oder Kosinus (c()) nutzen. Hierzu das Script, womit jetzt Berechnungen komplexer Ausdrücke nichts mehr im Wege steht:

# Demonstriert die Verwendung von Here-Dokumenten
# Name : ahere4
if [ $# == 0 ]
then
   echo "usage: $0 Ausdruck"
   exit 1
fi
# Option -l für die mathematische Bibliothek
bc -l <<CALC
$*
quit
CALC

Das Script bei der Ausführung:

you@host > ./ahere4 123.12/5
24.62400000000000000000
you@host > ./ahere4 1234.3*2
2468.6
you@host > ./ahere4 '(12.34–8.12)/2'
2.11000000000000000000
you@host > ./ahere4 sqrt\(24\)
4.89897948556635619639
you@host > var=`./ahere4 1234.1234*2*1.2`
you@host > echo $var
2961.89616

Rheinwerk Computing

5.3.5 Here-Dokumente mit read verwenden  downtop

Wie beim Auslesen einer Datei mit read können Sie selbstverständlich auch Here-Dokumente verwenden. Dies verhält sich dann so, als würde zeilenweise aus einer Datei gelesen. Und damit nicht nur die erste Zeile gelesen wird, geht man exakt genauso wie beim zeilenweise Einlesen einer Datei vor. Hier die Syntax:

while read line
do
   ...
done <<TEXT_MARKE
line1
line2
...
line_n
TEXT_MARKE

In der Praxis lässt sich diese Inline-Umlenkung mit read folgendermaßen einsetzen:

# Demonstriert die Verwendung von Here-Dokumenten und read
# Name : ahere5
i=1
while read line
do
   echo "$i. Zeile : $line"
   i=`expr $i + 1`
done <<TEXT
Eine Zeile
`date`
Homeverzeichnis $HOME
Das Ende
TEXT

Das Script bei der Ausführung:

you@host > ./ahere5
1. Zeile : Eine Zeile
2. Zeile : Fr Mär  4 03:23:22 CET 2005
3. Zeile : Homeverzeichnis /home/tot
4. Zeile : Das Ende

Rheinwerk Computing

5.3.6 Die Variable IFS  downtop

Die Variable IFS (Internal Field Separator) aus Ihrer Shell-Umgebung wurde in den letzten Abschnitten schon öfters erwähnt. Insbesondere scheint IFS eine spezielle Bedeutung bei der Ein- und Ausgabe von Daten als Trennzeichen zu haben. Wenn Sie allerdings versuchen, den Inhalt der Variablen mit echo auszugeben, werden Sie wohl nicht besonders schlau daraus. Für Abhilfe kann hierbei das Kommando od sorgen, mit dem Sie den Inhalt der Variablen in hexadezimaler oder ASCII-Form betrachten können.

you@host > echo -n "$IFS" | od -x
0000000 0920 000a
0000003

0000000 ist hierbei das Offset der Zahlenreihe, in der sich drei hexadezimale Werte befinden, die IFS beinhaltet. Damit das Newline von echo nicht mit enthalten ist, wurde hier die Option –n verwendet. Die Werte sind:

09 20 00 0a

Der Wert 00 hat hier keine Bedeutung. Jetzt können Sie einen Blick auf eine ASCII-Tabelle werfen, um festzustellen, für welches Sonderzeichen diese hexadezimalen Werte stehen.

gp  09 = Tabulator
gp  20 = Leerzeichen
gp  0a = Newline-Zeichen (neue Zeile)

Das Ganze gelingt mit od auch im ASCII-Format, nur dass das Leerzeichen als ein »Leerzeichen« angezeigt wird:

you@host > echo -n "$IFS" | od -c
0000000      \t  \n
0000003

Diese voreingestellten Trennzeichen der Shell dienen der Trennung der Eingabe für den Befehl read, der Variablen- sowie auch der Kommando-Substitution. Wenn Sie also z. B. read folgendermaßen verwenden

you@host > read nachname vorname alter
Wolf Jürgen 30
you@host > echo $vorname
Jürgen
you@host > echo $nachname
Wolf
you@host > echo $alter
30

so verdanken Sie es der Variablen IFS, dass hierbei die entsprechenden Eingaben an die dafür vorgesehenen Variablen übergeben wurden.

Häufig war aber in den Kapiteln zuvor die Rede davon, die Variable IFS den eigenen Bedürfnissen anzupassen – sprich: das oder die Trennzeichen zu verändern. Wollen Sie beispielsweise, dass bei der Eingabe von read ein Semikolon statt eines Leerzeichens als Trennzeichen dient, so lässt sich dies einfach erreichen:

you@host > BACKIFS="$IFS"
you@host > IFS=\;
you@host > echo -n "$IFS" | od -c
0000000   ;
0000001
you@host > read nachname vorname alter
Wolf;Jürgen;30
you@host > echo $vorname
Jürgen
you@host > echo $nachname
Wolf
you@host > echo $alter
30
you@host > IFS=$BACKIFS

Zuerst wurde eine Sicherung der Variablen IFS in BACKIFS vorgenommen. Der Vorgang, ein Backup von IFS zu erstellen, und das anschließende Wiederherstellen ist wichtiger, als dies auf den ersten Blick erscheint. Unterlässt man es, kann man sich auf das aktuelle Terminal nicht mehr verlassen, da einige Programme nur noch Unsinn fabrizieren.

Als Nächstes übergeben Sie im Beispiel der Variablen IFS ein Semikolon (geschützt mit einem Backslash). Daraufhin folgt dasselbe Beispiel wie zuvor, nur mit einem Semikolon als Trennzeichen. Am Ende stellen wir den ursprünglichen Wert von IFS wieder her.

Natürlich spricht auch nichts dagegen, IFS mit mehr als einem Trennzeichen zu versehen, beispielsweise:

you@host > IFS=$BACKIFS
you@host > IFS=:,
you@host > read nachname vorname alter
Wolf,Jürgen:30
you@host > echo $vorname
Jürgen
you@host > echo $nachname
Wolf
you@host > echo $alter
30
you@host > IFS=$BACKIFS

Im Beispiel wurde IFS mit den Trennzeichen : und , definiert. Wollen Sie bei einer Eingabe mit read, dass IFS nicht immer die führenden Leerzeichen (falls vorhanden) entfernt, so müssen Sie IFS nur mittels IFS= auf »leer« setzen:

you@host > IFS=$BACKIFS
you@host > read var
          Hier sind führende Leerzeichen vorhanden
you@host > echo $var
Hier sind führende Leerzeichen vorhanden
you@host > IFS=
you@host > read var
          Hier sind führende Leerzeichen vorhanden
you@host > echo $var
          Hier sind führende Leerzeichen vorhanden
you@host > IFS=$BACKIFS

Gleiches wird natürlich auch gern bei einer Variablen-Substitution verwendet. Im folgenden Beispiel wird die Variable durch ein Minuszeichen als Trenner zerteilt und mittels set auf die einzelnen Positionsparameter zerstückelt.

# Demonstriert die Verwendung von IFS
# Name : aifs1
# IFS sichern
BACKIFS="$IFS"
# Minuszeichen als Trenner
IFS=-
counter=1
var="Wolf-Jürgen-30-Bayern"
# var anhand von Trennzeichen in IFS auftrennen
set $var
# Ein Zugriff auf $1, $2, ... wäre hier auch möglich
for daten in "$@"
do
   echo "$counter. $daten"
   counter=`expr $counter + 1`
done
IFS=$BACKIFS

Das Script bei der Ausführung:

you@host > ./aifs1
1. Wolf
2. Jürgen
3. 30
4. Bayern

Wenn Sie das Script ein wenig umschreiben, können Sie hiermit zeilenweise alle Einträge der Shell-Variablen PATH ausgeben lassen, welche ja durch einen Doppelpunkt getrennt werden.

# Demonstriert die Verwendung von IFS
# Name : aifs2
# IFS sichern
BACKIFS="$IFS"
# Minuszeichen als Trenner
IFS=:
# PATH anhand von Trennzeichen in IFS auftrennen
set $PATH
for path in "$@"
do
   echo "$path"
done
IFS=$BACKIFS

Das Script bei der Ausführung:

you@host > ./aifs2
/home/tot/bin
/usr/local/bin
/usr/bin
/usr/X11R6/bin
/bin
/usr/games
/opt/gnome/bin
/opt/kde3/bin
/usr/lib/java/jre/bin

Natürlich hätte man dies wesentlich effektiver mit folgender Zeile lösen können:

you@host > echo $PATH | tr ':' '\n'

Zu guter Letzt sei erwähnt, dass die Variable IFS noch recht häufig in Verbindung mit einer Kommando-Substitution verwendet wird. Wollen Sie etwa mittels grep nach einem bestimmten User in /etc/passwd suchen, wird meistens eine Ausgabe wie folgt genutzt:

you@host > grep you /etc/passwd
you:x:1001:100::/home/you:/bin/bash

Dies in die Einzelteile zu zerlegen, sollte Ihnen jetzt mit der Variablen IFS nicht mehr schwer fallen.

# Demonstriert die Verwendung von IFS
# Name : aifs3
# IFS sichern
BACKIFS="$IFS"
# Minuszeichen als Trenner
IFS=:
if [ $# -lt 1 ]
then
   echo "usage: $0 User"
   exit 1
fi
# Ausgabe anhand von Trennzeichen in IFS auftrennen
set `grep ^$1 /etc/passwd`
echo "User             : $1"
echo "User-Nummer      : $3"
echo "Gruppen-Nummer   : $4"
echo "Home-Verzeichnis : $6"
echo "Start-Shell      : $7"
IFS=$BACKIFS

Das Script bei der Ausführung:

you@host > ./aifs3 you
User             : you
User-Nummer      : 1001
Gruppen-Nummer   : 100
Home-Verzeichnis : /home/you
Start-Shell      : /bin/bash
you@host > ./aifs3 tot
User             : tot
User-Nummer      : 1000
Gruppen-Nummer   : 100
Home-Verzeichnis : /home/tot
Start-Shell      : /bin/ksh
you@host > ./aifs3 root
User             : root
User-Nummer      : 0
Gruppen-Nummer   : 0
Home-Verzeichnis : /root
Start-Shell      : /bin/ksh

Rheinwerk Computing

5.3.7 Arrays einlesen mit read (Bash und Korn-Shell only)  downtop

Arrays lassen sich genauso einfach einlesen wie normale Variablen, nur dass man hier den Index beachten muss. Hierzu nur ein Script, da die Arrays bereits in Abschnitt 2.5 ausführlich behandelt wurden.

# Demonstriert die Verwendung von read mit Arrays
# Name : ararray
typeset -i i=0
while [ $i -lt 5 ]
do
   printf "Eingabe machen : "
   read valarr[$i]
   i=i+1
done
echo "Hier, die Eingaben ..."
i=0
while [ $i -lt 5 ]
do
   echo ${valarr[$i]}
   i=i+1
done

Das Script bei der Ausführung:

you@host > ./ararray
Eingabe machen : Testeingabe1
Eingabe machen : Noch eine Eingabe
Eingabe machen : Test3
Eingabe machen : Test4
Eingabe machen : Und Ende
Hier, die Eingaben ...
Testeingabe1
Noch eine Eingabe
Test3
Test4
Und Ende

Selbstverständlich lassen sich Arrays ganz besonders im Zusammenhang mit zeilenweisem Einlesen aus einer Datei mit read (siehe Abschnitt 5.3.2) verwenden:

typeset -i i=0
while read vararray[$i]
do
   i=i+1
done < datei_zum_einlesen

Damit können Sie anschließend bequem mithilfe des Feldindexes auf den Inhalt einzelner Zeilen zugreifen und diesen weiterverarbeiten.

In der Bash haben Sie außerdem die Möglichkeit, mit read einzelne Zeichenketten zu zerlegen und diese Werte direkt in eine Variable zu legen. Die einzelnen Zeichenketten (abhängig wieder vom Trennzeichen IFS) werden dann in extra Felder (einem Array) aufgeteilt. Hierfür müssen Sie read allerdings mit der Option –a aufrufen:

read -a array <<TEXTMARKE
$variable
TEXTMARKE

Mit diesem Beispiel können Sie ganze Zeichenketten in ein Array aufsplitten (immer abhängig davon, welche Trennzeichen Sie verwenden).


Rheinwerk Computing

5.3.8 Shell-abhängige Anmerkungen zu read  downtop

Da read ein Builtin-Kommando der Shell ist, gibt es hier zwischen den einzelnen Shells noch einige unterschiedliche Funktionalitäten.

Ausgabetext in read integrieren (Bash)

Verwenden Sie bei der Funktion read die Option –p, können Sie den Ausgabetext (für die Frage) direkt in die read-Abfrage integrieren. Die Syntax:

read -p Ausgabetext var1 var2 var3 ... var_n

In der Praxis sieht dies so aus:

you@host > read -p "Ihr Vorname : " vorname
Ihr Vorname : Jürgen
you@host > echo $vorname
Jürgen

Ausgabetext in read integrieren (Korn-Shell)

Auch die Korn-Shell bietet Ihnen die Möglichkeit, eine Benutzerabfrage direkt in den read-Befehl einzubauen.

read var?"Ausgabetext"

In der Praxis:

you@host > read name?"Ihr Name bitte: "
Ihr Name bitte: Jürgen Wolf
you@host > echo $name
Jürgen Wolf

Default-Variable für read (Bash und Korn-Shell only)

Verwenden Sie read, ohne eine Variable als Parameter, wird die anschließende Eingabe in der Default-Variablen REPLY gespeichert.

you@host > read
Jürgen Wolf
you@host > echo $REPLY
Jürgen Wolf

Rheinwerk Computing

5.3.9 Einzelnes Zeichen abfragen  downtop

Eine häufig gestellte Frage lautet, wie man einen einzelnen Tastendruck abfragen kann, ohne (ENTER) zu drücken. Das Problem an dieser Sache ist, dass ein Terminal gewöhnlich zeilengepuffert ist. Bei einer Zeilenpufferung wartet das Script z. B. bei einer Eingabeaufforderung mit read so lange mit der Weiterarbeit, bis die Zeile mit einem (ENTER) bestätigt wird.

Da ein Terminal mit unzähligen Eigenschaften versehen ist, ist es am einfachsten, all diese Parameter mit dem Kommando stty und der Option raw auszuschalten. Damit schalten Sie Ihr Terminal in einen rohen Modus, womit Sie praktisch »nackte« Zeichen behandeln können. Sobald Sie fertig sind, ein Zeichen einzulesen, sollten Sie diesen Modus auf jeden Fall wieder mit –raw verlassen!

Wenn Sie Ihr Terminal in einen rohen Modus geschaltet haben, benötigen Sie eine Funktion, welche ein einzelnes Zeichen lesen kann. Die Funktion read fällt wegen der Zeilenpufferung aus. Einzige Möglichkeit ist das Kommando dd (Dump Disk), worauf man zunächst gar nicht kommen würde. dd liest eine Datei und schreibt den Inhalt mit wählbarer Blockgröße und verschiedenen Konvertierungen. Anstatt für eine Datei kann dd genauso gut für das Kopieren der Standardeingabe in die Standardausgabe verwandt werden. Hierzu reicht es, die Anzahl der Datensätze (count hier 1) anzugeben, und welche Blockgröße Sie in Bytes verwenden wollen (bs, auch hier 1 Byte). Da dd seine Meldung auf die Standardfehlerausgabe vornimmt, können Sie diese Ausgabe ins Datengrab schicken.

Hier ein Script, das unermüdlich auf den Tastendruck »q« für Quit zum Beenden einer Schleife wartet.

# Demonstriert das Einlesen einzelner Zeichen
# Name : areadchar
echo "Bitte eine Taste betätigen – mit q beenden"
char=''
# Terminal in den "rohen" Modus schalten
stty raw -echo
# In Schleife überprüfen, ob 'q' gedrückt wurde
while [ "$char" != "q" ]
do
   char=`dd bs=1 count=1 2>/dev/null`
done
# Den "rohen" Modus wieder abschalten
stty -raw echo
echo "Die Taste war $char"

Das Script bei der Ausführung:

you@host > ./areadchar
Bitte eine Taste betätigen – mit q beenden (Q)
Die Taste war q

Hinweis In der Bash können Sie auch die Abfrage einzelner Zeichen mit read und der Option -n vornehmen. Schreiben Sie etwa read -n 1 var, befindet sich in var das einzelne Zeichen. Allerdings unterstützt dies nur darstellbare ASCII-Zeichen. Bei Zeichen mit Escape-Folgen (siehe nächster Abschnitt) taugt auch dies nicht mehr.



Rheinwerk Computing

5.3.10 Einzelne Zeichen mit Escape-Sequenzen abfragen  downtop

Im Beispiel zuvor haben Sie gesehen, wie Sie einzelne Tastendrücke mithilfe der Kommandos stty und dd abfragen können. Aber sobald Sie hierbei Tastendrücke wie (F1), (F2), (ESC), (˝), (Ľ) usw. abfragen wollen, wird dies nicht mehr funktionieren. Testen Sie am besten selbst unser abgespecktes Script von eben:

# Demonstriert das Einlesen einzelner Zeichen
# Name : areadchar2
echo "Bitte eine Taste betätigen"
stty raw -echo
char=`dd bs=1 count=1 2>/dev/null`
stty -raw echo
echo "Die Taste war $char"

Das Script bei der Ausführung:

you@host > ./areadchar2
Bitte eine Taste betätigen (A)
Die Taste war A
you@host > ./areadchar2
Bitte eine Taste betätigen (ESC)
Die Taste war
you@host > ./areadchar2
Bitte eine Taste betätigen (F1)
Die Taste war
[A

Das Problem mit den Pfeil- oder Funktionstasten ist nun mal, dass hierbei Escape-Folgen zurückgegeben werden. Schlimmer noch, die Escape-Folgen sind häufig unterschiedlich lang und – um es noch schlimmer zu machen – abhängig vom Typ des Terminals und zusätzlich auch noch der nationalen Besonderheit (Umlaute und Ähnliches).

Somit scheint eine portable Lösung des Problems fast unmöglich, es sei denn, man kennt sich ein bisschen mit C aus. Da ich dies nicht voraussetzen kann, gebe ich Ihnen hier ein einfaches Rezept an die Hand, welches Sie bei Bedarf erweitern können.

Escape-Sequenzen des Terminals ermitteln

Zuerst müssen Sie sich darum kümmern, wie die Escape-Sequenzen für entsprechende Terminals aussehen. Welches Terminal Sie im Augenblick nutzen, können Sie mit echo $TERM in Erfahrung bringen. Im nächsten Schritt können Sie den Befehl infocmp verwenden, um vom entsprechenden Terminaltyp Informationen zu den Tasten zu erhalten.

you@host > echo $TERM
xterm
you@host > infocmp
#       Reconstructed via infocmp from file: /usr/share/terminfo/x/xterm
xterm|xterm terminal emulator (X Window System),
        am, bce, km, mc5i, mir, msgr, npc, xenl,
        colors#8, cols#80, it#8, lines#24, pairs#64,
        bel=^G, blink=\E[5m, bold=\E[1m, cbt=\E[Z, civis=\E[?25l,
        clear=\E[H\E[2J, cnorm=\E[?12l\E[?25h, cr=^M,
        csr=\E[%i%p1  %d;%p2  %dr, cub=\E[%p1  %dD, cub1=^H,
        cud=\E[%p1  %dB, cud1=^J, cuf=\E[%p1  %dC, cuf1=\E[C,
        cup=\E[%i%p1  %d;%p2  %dH, cuu=\E[%p1  %dA, cuu1=\E[A,
        cvvis=\E[?12;25h, dch=\E[%p1  %dP, dch1=\E[P, dl=\E[%p1  %dM,
        dl1=\E[M, ech=\E[%p1  %dX, ed=\E[J, el=\E[K, el1=\E[1K,
        enacs=\E(B\E)0, flash=\E[?5h$<100/>\E[?5l, home=\E[H,
        hpa=\E[%i%p1  %dG, ht=^I, hts=\EH, ich=\E[%p1  %d@,
        il=\E[%p1  %dL, il1=\E[L, ind=^J, indn=\E[%p1  %dS,
        invis=\E[8m, is2=\E[!p\E[?3;4l\E[4l\E>, kDC=\E[3;2~,
        kEND=\E[1;2F, kHOM=\E[1;2H, kIC=\E[2;2~, kLFT=\E[1;2D,
        kNXT=\E[6;2~, kPRV=\E[5;2~, kRIT=\E[1;2C, kb2=\EOE,
        kbs=\177, kcbt=\E[Z, kcub1=\EOD, kcud1=\EOB, kcuf1=\EOC,
        kcuu1=\EOA, kdch1=\E[3~, kend=\EOF, kent=\EOM, kf1=\EOP,
        kf10=\E[21~, kf11=\E[23~, kf12=\E[24~, kf13=\EO2P,
        ...

Hierbei werden Ihnen zunächst eine Menge Informationen um die Ohren gehauen, welche auf den ersten Blick wie Zeichensalat wirken. Bei genauerem Hinsehen aber können Sie alte Bekannte aus Abschnitt 5.2.4 zur Steuerung des Terminals mit tput wieder entdecken.


Hinweis   Wenn Sie über kein infocmp auf Ihrem Rechner verfügen, liegt das wahrscheinlich daran, dass das ncurses-devel-Paket nicht installiert ist. Diesem Paket liegt auch infocmp bei.


infocmp gibt Ihnen hier Einträge wie

cuu1=\E[A

zurück. Dass man daraus nicht sonderlich schlau wird, leuchtet ein. Daher sollten Sie die Manual-Page von terminfo(5) befragen (oder genauer absuchen), wofür denn hier cuu1 steht:

you@host > man 5 terminfo | grep cuu1
Formatiere terminfo(5) neu, bitte warten...
       cursor_up                 cuu1   up   up one line
       key_up                    kcuu1  ku   up-arrow key
       micro_up                  mcuu1  Zd   Like cursor_up in
             kcuf1=\E[C, kcuu1=\E[A, kf1=\E[M, kf10=\E[V,

Also, die Pfeil-nach-oben-Taste (cursor_up) haben wir hier. Und diese wird bei der xterm mit \E[A »dargestellt«. Das \E ist hierbei das Escape-Zeichen und besitzt den ASCII-Codewert 27. Somit sieht der C-Quellcode zum Überprüfen der Pfeil-nach-oben-Taste wie folgt aus (fett hervorgehoben ist hier die eigentliche Überprüfung, die Sie bei Bedarf erweitern können):

/* getkey.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <termios.h>
enum { ERROR=-1, SUCCESS, ONEBYTE };
/*Altes Terminal wiederherstellen*/
static struct termios BACKUP_TTY; /*Altes Terminal wiederherstellen*/
/* Eingabekanal wird so umgeändert, damit die Tasten einzeln */
/* abgefragt werden können */
int new_tty(int fd) {
   struct termios buffer;
/* Wir fragen nach den Attributen des Terminals und übergeben  */
/* diese dann an buffer. BACKUP_TTY dient bei Programmende zur */
/* Wiederherstellung der alten Attribute und bleibt unberührt. */
   if((tcgetattr(fd, &BACKUP_TTY)) == ERROR)
      return ERROR;
   buffer = BACKUP_TTY;
/* Lokale Flags werden gelöscht :               */
/*     ECHO = Zeichenausgabe auf Bildschirm     */
/*     ICANON = Zeilenorientierter Eingabemodus */
/*     ISIG = Terminal Steuerzeichen            */
   buffer.c_lflag = buffer.c_lflag & ~(ECHO|ICANON|ISIG);
/* VMIN=Anzahl der Bytes, die gelesen werden müssen, bevor    */
/* read() zurückkehrt In unserem Beispiel 1Byte für 1 Zeichen */
   buffer.c_cc[VMIN] = 1;
/* Wir setzen jetzt die von uns gewünschten Attribute */
   if((tcsetattr(fd, TCSAFLUSH, &buffer)) == ERROR)
      return ERROR;
   return SUCCESS;
}
/* Ursprüngliches Terminal wiederherstellen */
int restore_tty(int fd) {
   if((tcsetattr(fd, TCSAFLUSH, &BACKUP_TTY)) == ERROR)
      return ERROR;
   return SUCCESS;
}
int main(int argc, char **argv) {
   int rd;
   char c, buffer[10];
/* Setzen des neuen Modus */
   if(new_tty(STDIN_FILENO) == ERROR) {
      printf("Fehler bei der Funktion new_tty()\n");
      exit(EXIT_FAILURE);
   }
/* Erste Zeichen lesen */
   if(read(STDIN_FILENO, &c, 1) < ONEBYTE) {
      printf("Fehler bei read()\n");
      restore_tty(STDIN_FILENO);
      exit(EXIT_FAILURE);
   }
/* Haben wir ein ESC ('\E') gelesen? */
   if(c == 27) {
/* Jep eine Escape-Sequenz, wir wollen den Rest */
/* der Zeichen auslesen                         */
      rd=read(STDIN_FILENO, buffer, 4);
      /*String terminieren*/
      buffer[rd]='\0';
/* Hier erfolgt die Überprüfung des Tastendrucks*/
/* Wars der Pfeil-nach-oben \E[A   */
      if(strcmp(buffer,"[A") == SUCCESS)
         printf("Pfeil-nach-oben betätigt\n");
/* Nein, keine Escape-Sequenz */
   }
   else {
      if((c < 32) || (c == 127))
         printf("--> %d\n",c); /* Numerischen Wert ausgeben */
      else
         printf("--> %c\n",c); /* Zeichen ausgeben */
   }
   restore_tty(STDIN_FILENO);
   return EXIT_SUCCESS;
}

Wird das Programm kompiliert, übersetzt und anschließend ausgeführt, erhalten Sie folgende Ausgabe:

you@host > gcc -Wall -o getkey getkey.c
you@host > ./getkey
(1) --> 1
you@host > ./getkey
(A) --> A
you@host > ./getkey
(˝) Pfeil-nach-oben betätigt

Wie Sie jetzt im Beispiel vorgegangen sind, können Sie auch mit anderen Pfeil- und Funktionstasten vorgehen. Den Pfeil-nach-rechts können Sie z. B. wie folgt implementieren:

      if(strcmp(buffer,"[D") == SUCCESS)
         printf("Pfeil-nach-rechts\n");

Aber anstatt einer Ausgabe empfehle ich Ihnen, einen Rückgabewert mittels return zu verwenden, etwa:

...
/* Hier erfolgt die Überprüfung des Tastendrucks*/
/* Wars der Pfeil-nach-oben \E[A   */
      if(strcmp(buffer,"[A") == SUCCESS) {
         restore_tty(STDIN_FILENO);
         return 10; /* Rückgabewert für unser Shellscript */
      }
/* Wars der Pfeil-nach-links */
      if(strcmp(buffer,"[D") == SUCCESS) {
         restore_tty(STDIN_FILENO);
         return 11;  /* Rückgabewert für unser Shellscript */
      }
...

Damit können Sie das Beispiel auch mit einem Shellscript verwenden, indem Sie die Variable $? abfragen. Hier das Script:

# Demonstriert das Einlesen einzelner Zeichen
# Name : areadchar3
echo "Bitte eine Taste betätigen"
./getkey
ret=$?
if [ $ret -eq 0 ]
then
   echo "Keine Pfeiltaste"
fi
if [ $ret -eq 10 ]
then
   echo "Die Pfeil-nach-oben-Taste wars"
fi
if [ $ret -eq 11 ]
then
   echo "Die Pfeil-nach-links-Taste wars"
 fi

Übersetzen Sie das C-Programm von Neuem mit den geänderten Zeilen Code und führen Sie das Shellscript aus:

you@host > ./areadchar3
Bitte eine Taste betätigen
(d)--> d
Keine Pfeiltaste
you@host > ./areadchar3
Bitte eine Taste betätigen
(˝)Die Pfeil-nach-oben-Taste wars
you@host > ./areadchar3
Bitte eine Taste betätigen
(ć)Die Pfeil-nach-links-Taste wars

Mit dem C-Programm haben Sie quasi Ihr eigenes Tool geschrieben, welches Sie am besten in ein PATH-Verzeichnis kopieren und auf welches Sie somit jederzeit zurückgreifen und natürlich erweitern können/sollen.


Hinweis   Natürlich wurde das Thema hier nur gestreift, aber es würde keinen Sinn ergeben, noch mehr einzusteigen, da ich von Ihnen nicht auch noch C-Kenntnisse verlangen kann. Aber Sie konnten hierbei schon sehen, dass Sie mit zusätzlichen Programmierkenntnissen noch viel weiter kommen können. Fehlt ein bestimmtes Tool, programmiert man eben selbst eins.

Und noch ein Hinweis   Noch mehr Lust auf die C-Programmierung bekommen? Ich könnte Ihnen mein Buch »C von A bis Z« empfehlen oder etwas zur Linux-UNIX-Programimerung. Auch hierzu finden Sie ein Buch aus meiner Feder (»Linux-Unix-Programmierung«). Beide Bücher sind bei Galileo Press erschienen.



Rheinwerk Computing

5.3.11 Passworteingabe  toptop

Wollen Sie in Ihrem Script eine Passworteingabe vornehmen und darauf verzichten, dass die Eingabe auf dem Bildschirm mit ausgegeben wird, können Sie auch hierfür stty verwenden. Hierbei reicht es aus, die Ausgabe (echo) abzuschalten und nach der Passworteingabe wieder zu aktivieren.

stty -echo
# Passwort einlesen
stty echo

Ein Script als Beispiel:

# Demonstriert das Einlesen einzelner Zeichen
# Name : anoecho
printf "Bitte Eingabe machen: "
# Ausgabe auf dem Bildschirm abschalten
stty -echo
read passwort
# Ausgabe auf dem Bildschirm wieder einschalten
stty echo
printf "\nIhre Eingabe lautete : %s\n" $passwort

Das Script bei der Ausführung:

you@host > ./anoecho
Bitte Eingabe machen:
Ihre Eingabe lautete: k3l5o6i8


Ihre Meinung

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.

 << zurück
  
  Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Shell-Programmierung
Shell-Programmierung
bestellen
 Buchtipps
Zum Rheinwerk-Shop: Shell-Programmierung






 Shell-Programmierung


Zum Rheinwerk-Shop: Linux-Server






 Linux-Server


Zum Rheinwerk-Shop: Das Komplettpaket LPIC-1 & LPIC-2






 Das Komplettpaket
 LPIC-1 & LPIC-2


Zum Rheinwerk-Shop: Linux-Hochverfügbarkeit






 Linux-
 Hochverfügbarkeit


Zum Rheinwerk-Shop: Linux Handbuch






 Linux Handbuch


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
InfoInfo





Copyright © Rheinwerk Verlag GmbH 2005
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern