Bash-Skripte erledigen in der Regel nur Kleinigkeiten. Administratoren und Programmierer benutzen die Bash praktisch ausschließllich, um alltägliche Standardaufgaben zu automatisieren, für die es sich nicht lohnt, ein ausgewachsenes Binärprogramm in einer Hochsprache zu schreiben.
Ein großes Manko fast aller Skripte stellen das rudimentäre Benutzer-Interface und die meist schlechte Benutzerführung dar. Der von KDE und Gnome verwöhnte Anwender findet es umständlich, erst eine Textkonsole zu öffnen und dann knappe Fragen mit Tastatureingaben zu beantworten. Die Kommandozeile schreckt insbesondere Einsteiger ab, die mit den Linux-Befehlen noch nicht vertraut sind.
Standardisierte Menüs
Das Programmdialog ermöglicht die Darstellung eines ansprechenderen Benutzer-Interfaces. Dialog war anfangs nur für die Kommandozeile gedacht und baute aus farbigen Hintergründen und einigen ASCII-Zeichen Rahmen und Menüs. Mit der zunehmenden Verbreitung von KDE und Gnome hat auch Dialog nachgezogen und verfügt mittlerweile über Ableger gebildet: kdialog für KDE und gdialog für Gnome.
Dialog und seine grafischen Pendants werden komplett über die Kommandozeile gesteuert. Genügen bei einfachen Abfragen noch eine Hand voll Parameter, gestalten sich große Auswahllisten oder Menüs schnell unübersichtlich. Dialog baut dann selbständig anhand der Kommandozeilen-Parameter die Bedienoberfläche auf und übernimmt die komplette Interaktion mit dem Benutzer.
Bei Menüs oder Abfragen liefert Dialog die Auswahl des Anwenders an das aufrufende Programm oder Skript zurück. Beim Ur-Dialog für die Kommandozeile muss dies über den Rückgabewert von Dialog oder die Standard-Fehlerausgabe (stderr) laufen. Die Standardausgabe (stdout) benötigt Dialog für die Interaktion mit dem Benutzer.
Grafische Oberfläche gefällig?
Bei den grafischen Varianten Gdialog und Kdialog wäre dies prinzipiell nicht erforderlich: Sie könnten die Auswahlen per Standard-Ausgabe zurück geben. Aus Kompatibilitätsgründen zu Dialog verwendet Gdialog jedoch weiterhin die Standard-Fehlerausgabe. Leider verwendet Kdialog statt dessen die Standardausgabe, was eine portable Programmierung erschwert.
Als Beispiel soll der Termin-Client eines Terminüberwachungs-Skripts dienen, das bereits im siebten Teil der Programming Corner aus LinuxUser 02/2002 [1] vorgestellt wurde. Der Client kommuniziert über Dialog, Gdialog oder Kdialog mit dem Benutzer. Währenddessen läuft der Server still im Hintergrund. Listing 1 enthält den um einige Kommentare verkürzten Quellcode des Termin-Clients.
Listing 1
Der Termin-Client
001 #!/bin/bash
002 if [ -n "`which gdialog 2>/dev/null`" ]; then
003 Dialog="`which gdialog`"
004 DialogRedirect="2>~/.termine.in"
005 elif [ -n "`which kdialog 2>/dev/null`" ]; then
006 Dialog="`which kdialog`"
007 DialogRedirect="1>~/.termine.in"
008 elif [ -n "`which dialog 2>/dev/null`" ]; then
009 Dialog="`which dialog`"
010 DialogRedirect="2>~/.termine.in"
011 else
012 echo "Sie benötigen dialog, kdialog oder gdialog,"
013 echo "um dieses Programm benutzen zu können."
014 exit 1
015 fi
016
017 COLUMNS=80
018 LINES=24
019
020 AuswahlMenue ()
021 {
022 Titel="\"$1\""
023 Tasten="$2"
024 Namen="$3"
025
026 IFS=$'\t'
027 set – $Tasten
028 i=1
029 while [ "$#" -gt "0" ]; do
030 Taste[$i]="\"$1\""
031 shift
032 i=$[$i+1]
033 done
034 Elemente="$i"
035
036 IFS=$'\t'
037 set – $Namen
038 i=1
039 while [ "$#" -gt "0" ]; do
040 Name[$i]="\"$1\""
041 shift
042 i=$[$i+1]
043 done
044
045 Menu=""
046 for ((i=1; $i<$Elemente; i=$[$i+1])); do
047 Menu="${Menu}${Taste[$i]}${IFS}${Name[$i]}${IFS}"
048 done
049
050 Overhead="7"
051 if [ "$[$Elemente+$Overhead]" -lt "$LINES" ]; then
052 DisplayLines="$[$Elemente+$Overhead]"
053 else
054 DisplayLines="$LINES"
055 fi
056 DialogParms="--menu $Titel $DisplayLines $[$COLUMNS-8] $[$Elemente-1] $Menu"
057 eval $Dialog $DialogParms $DialogRedirect
058 read < ~/.termine.in
059 }
060 TextEingabe ()
061 {
062 DialogParms="--inputbox \"$1\" 8 $[$COLUMNS-8] \"$2\""
063 eval $Dialog $DialogParms $DialogRedirect
064 read < ~/.termine.in
065 if [ "$REPLY" = "" ]; then
066 REPLY="$2"
067 fi
068 }
069 JaNein ()
070 {
071 DialogParms="--yesno \"$1\" 5 $[$COLUMNS-8]"
072 eval $Dialog $DialogParms $DialogRedirect
073 }
074 Fehler ()
075 {
076 DialogParms="--msgbox \"$1\" 5 $[$COLUMNS-8]"
077 eval $Dialog $DialogParms $DialogRedirect
078 }
079 TerminBearbeiten ()
080 {
081 Nr="$1"
082
083 while true; do
084 Keys="d${IFS}v${IFS}b${IFS}t${IFS}c${IFS}l${IFS}z${IFS}"
085 Menu="Datum: `date -d ${Datum[$Nr]} +%d.%m.%Y`${IFS}Vorwarnung: ${Vorwarnzeit[$Nr]} Tage${IFS}Betreff: ${Bezeichnung[$Nr]}${IFS}Text: ${Text[$Nr]}${IFS}CCs: ${CCs[$Nr]}${IFS}Löschen${IFS}Zurück"
086
087 AuswahlMenue "Termin bearbeiten:" "$Keys" "$Menu"
088 Auswahl="$REPLY"
089
090 case $Auswahl in
091 z|"")
092 break
093 ;;
094 l)
095 JaNein "Termin löschen: Sind Sie sicher?"
096
097 if [ "$?" = "0" ]; then
098 Termine=$[$Termine-1]
099
100 for ((i=$Nr; $i < $Termine; i=$[$i+1])); do
101 Datum[$i]=${Datum[$[$i+1]]}
102 Vorwarnzeit[$i]=${Vorwarnzeit[$[$i+1]]}
103 Bezeichnung[$i]=${Bezeichnung[$[$i+1]]}
104 Text[$i]=${Text[$[$i+1]]}
105 CCs[$i]=${CCs[$[$i+1]]}
106 done
107 fi
108 break
109 ;;
110 d)
111 TextEingabe "Datum, in der Form yyyy-mm-dd:" "`date -d ${Datum[$Nr]} +%Y-%m-%d`"
112 Datum[$Nr]="`date -d $REPLY +%Y%m%d`"
113 ;;
114 v)
115 TextEingabe "Vorwarnzeit in Tagen:" "${Vorwarnzeit[$Nr]}"
116 Vorwarnzeit[$Nr]="$REPLY"
117 ;;
118 b)
119 TextEingabe "Kurze Bezeichnung des Termins:" "${Bezeichnung[$Nr]}"
120 Bezeichnung[$Nr]="$REPLY"
121 ;;
122 t)
123 TextEingabe "Ausführliche Beschreibung, Anmerkungen:" "${Text[$Nr]}"
124 Text[$Nr]="$REPLY"
125 ;;
126 c)
127 TextEingabe "Zusätzliche Empfänger der Warnung (Komma-Trennung):" "${CCs[$Nr]}"
128 CCs[$Nr]="$REPLY"
129 ;;
130 esac
131 done
132 }
133
134 ##### Hauptprogramm #####
135
136 if [ -r ~/.termine ]; then
137 IFS=$'\r'
138 read -a TerminListe << EOF
139 `grep -v "^#" ~/.termine | tr "\n" "$IFS"`
140 EOF
141
142 IFS=$'\t'
143 i=1
144 while [ "${TerminListe[$i]}" != "" ]; do
145 set – ${TerminListe[$i]}
146 Datum[$i]=$1
147 Vorwarnzeit[$i]=$2
148 Bezeichnung[$i]=$3
149 Text[$i]=$4
150 CCs[$i]=$5
151 i=$[$i+1]
152 done
153 Termine="$i"
154 else
155 MailAdresse="${LOGNAME}@localhost"
156 Termine=1
157 fi
158
159 while true; do
160 IFS=$'\t'
161 Keys="0${IFS}\$Keys${IFS}n${IFS}q${IFS}x${IFS}"
162 Menu="Adresse: ${MailAdresse}\$Menu${IFS}Neuer Termin${IFS}Speichern und Ende${IFS}Ende ohne Speichern"
163 for ((i=1; $i < $Termine; i=$[$i+1])); do
164 Keys="${Keys/\$Keys/${IFS}$i\$Keys}"
165 Menu="${Menu/\$Menu/${IFS}`date -d ${Datum[$i]} +%d.%m.%Y`: ${Bezeichnung[$i]}\$Menu}"
166 done
167 Keys="${Keys/\$Keys}"
168 Menu="${Menu/\$Menu}"
169
170 AuswahlMenue "Hauptmenü" "$Keys" "$Menu"
171 Auswahl="$REPLY"
172
173 case $Auswahl in
174 q)
175 if [ ! -e ~/.termine ]; then
176 touch ~/.termine 2>/dev/null
177 fi
178 if [ -w ~/.termine ]; then
179 echo "$MailAdresse" > ~/.termine
180 IFS=$'\t'
181 for ((i=1; $i < $Termine; i=$[$i+1])); do
182 cat >> ~/.termine << EOF
183 ${Datum[$i]}${IFS}${Vorwarnzeit[$i]}${IFS}${Bezeichnung[$i]}${IFS}${Text[$i]}${IFS}${CCs[$i]}
184 EOF
185 done
186 break
187 else
188 Fehler "Die Termine-Datei (~/.termine) lässt sich nicht schreiben."
189 fi
190 ;;
191 x|"")
192 exit 1
193 ;;
194 n)
195 Datum[$Termine]="`date +%Y%m%d`"
196 Vorwarnzeit[$Termine]="2"
197 Bezeichnung[$Termine]=""
198 Text[$Termine]=""
199 CCs[$Termine]=""
200 Termine=$[$Termine+1]
201 TerminBearbeiten $[$Termine-1]
202 ;;
203 0)
204 TextEingabe "Ihre Email-Adresse:" "$MailAdresse"
205 MailAdresse="$REPLY"
206 ;;
207 *)
208 TerminBearbeiten $Auswahl
209 ;;
210 esac
211 done



