Login
Login-Name Passwort


 
Newsletter
Werbung

Mo, 17. Juli 2000, 00:00

GNU Readline Bibliothek

Dieser Artikel stellt die GNU Readline Bibliothek vor.

Die C-Standardbibliothek verfügt leider nicht über (für den Benutzer) komfortable Eingabefunktionen. Auf die Verwendung von gets() sollte man ohnehin verzichten, da es zu Puffer-Überläufen kommen kann. Auch die sicherere Variante fgets() weist noch mehr als genug Nachteile auf, da auch hier kein Puffer, der genau so groß wie nötig ist, von der Funktion selbst allokiert wird.

Immerhin ein Stückchen besser geeignet wäre hier die GNU-Erweiterung getline(), aber auch hier ist das Editieren einer Eingabezeile für den Benutzer nicht annähernd so komfortabel wie z.B. in der Bash. Dieses Defizit behebt die Readline-Bibliothek, deren Handhabung beinahe schon als trivial bezeichnet werden kann.

Fangen wir mal mit einem einfachen Beispielprogramm an, das die Readline-Bibliothek nutzt, und nehmen dieses dann Stück für Stück auseinander.

#include <stdio.h>
#include <stdlib.h>
#include <readline/readline.h>
#include <readline/history.h>
int main (void)
{
 char *buf;
 rl_bind_key ('\t', rl_insert);
 while (1)
 {
  buf = readline ("> ");
  /* Keine Eingabe mehr */
  if (buf == NULL)
   break;
  /* Eingabezeile ist nicht leer */
  if (buf[0] != '\0')
  {
   add_history (buf);
   printf ("Sie sagten: %s\n", buf);
  }
  free (buf);
 }
 putchar ('\n');
 exit (EXIT_SUCCESS);
 return 0;
}

Die Kernfunktion ist readline(). Ihr wird als einziger Parameter ein char* übergeben wird, der das auszugebende Prompt festlegt. Zurückgegeben wird ein char* auf einen mit malloc() erzeugten String, der eine Eingabezeile enthällt. Das Freigeben dieses Strings durch free() muss man selbst durchführen. Wenn keine Eingabezeile mehr vorhanden ist, wird NULL zurückgegeben.

Standardmäßig ist die Tabulator-Taste mit der Dateinamens-Vervollständigung belegt. In unserem Programm möchten wir das nicht, daher verbinden wir mit Hilfe von rl_bind_key() die Tab-Taste mit der Funktion rl_insert(), die bewirkt, dass das entsprechende Zeichen einfach in den Eingabepuffer eingefügt wird. Das Schreiben einer Funktion, die den Eingabepuffer manipuliert, ist kein großes Hexenwerk, aber in diesem Artikel wird darauf nicht eingegangen. Wenn Sie daran interessiert sind, stellt Ihnen die GNU Texinfo-Dokumentation alle benötigten Informationen bereit.

Auch eine History-Funktion wird durch die Readline-Bibliothek bereitgestellt. Lediglich die Funktion add_history() muss dazu aufgerufen werden. Im obigen Beispiel werden leere Zeilen nicht in die History eingefügt, da das im Allgemeinen keinen Sinn macht und die meisten Programme dies ebenso handhaben.

Vielleicht wundern Sie sich, warum ich an das Ende der main()-Funktion sowohl einen exit() Funktionsaufruf als auch eine return-Anweisung schreibe. Ich verwende exit(), weil es Systeme gibt, die den Rückgabewert von main() ignorieren. Ich verwende zusätzlich return, weil es Compiler gibt, die sonst eine Warnung ausgeben.

Das obige Programm kann beispielsweise mit

gcc -W -g -O2 -lreadline -o rl rl.c

compiliert werden (die Flags -W, -g und -O2 sind dabei natürlich nicht erforderlich und Sie können diese durch Ihre persönlichen Lieblingsflags ersetzen).

Ein Programm mit der Readline-Bibliothek zu schreiben, ist scheinbar nicht schwierig. Wir wollen uns daher mal ansehen, was unter anderem beachtet werden muss, wenn man einem Programm die Unterstützung dieser Bibliothek hinzufügen will. Als Ausgangspunkt nehmen wir dazu das folgende Programm, das in Präfix-Notation vorliegende arithmetische Ausdrücke auswertet.

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
void err (const char *s)
{
 fprintf (stderr, "%s\n", s);
 exit (EXIT_FAILURE);
}
/* Einen Ausdruck einlesen und auswerten */
double expr (int eof_is_okay)
{
 int c;
 char buf[32];
 /* Ein brauchbares Zeichen einlesen */
 do
 {
 c = getchar ();
 if (feof (stdin))
 {
 if (!eof_is_okay)
 err ("Unerwartetes Dateiende");
 else
 exit (EXIT_SUCCESS);
 }
 }
 while (isspace (c));
 switch (c)
 {
 case '+': return expr (0) + expr (0);
 case '*': return expr (0) * expr (0);
 case '-': return expr (0) - expr (0);
 case '/':
 {
 double first = expr (0);
 double second = expr (0);
 if (second == 0.0)
  err ("Division durch Null");
 return first / second;
 }
 default:
 {
 int i;
 if (!isdigit (c))
 break;
 /* Es ist eine Zahl */
 ungetc (c, stdin);
 scanf ("%d", &i);
 return (double) i;
 }
 }
 sprintf (buf, "Unbekanntes Zeichen: `%c'", c);
 err (buf);
 return 0.0;
}
int main (void)
{
 while (1)
 printf ("%lf\n", expr (1));
 return 0;
}

Dieses Programm könnte man z.B. mit

gcc -W -g -O2 -o calc calc.c

übersetzen. Ein Beispiel zum Programm:

$ ./calc
+ 1 1
2.000000
* 2 + 3 2
10.000000
/ 1 4
0.250000

Das Programm macht sowohl von getchar() als auch von scanf() Gebrauch. das Verhalten von getchar() kann relativ einfach simuliert werden. Wir werden uns dazu eine Funktion next_char() schreiben, die jeweils das zurückgibt, was auch getchar() liefern würde.

Kommentare (Insgesamt: 0 || Kommentieren )
Pro-Linux
Pro-Linux @Facebook
Neue Nachrichten
Werbung