Copyright © 1995 by O'Reilly/International Thomson Verlag

Bitte denken Sie daran: Sie dürfen zwar die Online-Version ausdrucken, aber diesen Druck nicht fotokopieren oder verkaufen.

Wünschen Sie mehr Informationen zu der gedruckten Version des Buches "Linux - Wegweiser zur Installation & Konfiguration", dann klicken Sie hier.


Kapitel 6

Tools für die Programmierung

Neben den Programmiersprachen und Compilern gibt es noch eine Fülle von Programmiertools, u.a. Libraries, Programme zum Erstellen von Bedienoberflächen, Debugger und andere Tools, die Sie bei der Programmierung unterstützen sollen. Wir werden in diesem Abschnitt einige der interessanteren Tools besprechen, damit Sie einen Eindruck davon bekommen, was überhaupt verfügbar ist.

Debugger

Es gibt etliche interaktive Debugger für Linux. Der De-facto-Standard ist gdb , den wir ausführlich besprochen haben.

Neben gdb existieren einige andere Debugger, die dem gdb sehr ähnlich sind. xxgdb ist eine Version von gdb mit einer Schnittstelle zum X Window System, die dem Debugger xdbx ähnelt, den man auf anderen UNIX-Systemen findet. Diese X-basierte Schnittstelle zeigt ein Fenster, das mehrfach unterteilt ist. Ein Ausschnitt stellt die normale Textschnittstelle von gdb dar, so daß Sie von Hand Befehle eingeben können, um das System zu bedienen. Ein anderer Ausschnitt zeigt automatisch die aktuelle Quelldatei an und hebt die aktuelle Textzeile hervor. Im Hauptfenster können Sie Breakpoints setzen und auswählen, durch den Quelltext blättern usw., während Sie Ihre Befehle direkt an den gdb schicken. Im xxgdb -Fenster erscheinen auch einige Schaltflächen, hinter denen sich häufig benutzte Befehle wie step , next usw. verbergen. Auf diese Weise können Sie mit Maus und Tastatur innerhalb einer einfach zu bedienenden X-Oberfläche Ihr Programm debuggen.

Ein anderer, dem xxgdb ähnlicher Debugger, ist UPS -- ein X-basierter Debugger, der auf einige UNIX-Plattformen portiert wurde. UPS ist wesentlich einfacher als xxgdb und bietet weniger Möglichkeiten, ist aber trotzdem ein guter Debugger mit einer weniger anspruchsvollen Lernkurve als gdb . UPS ist ausreichend für die meisten Anwendungen und einfaches Debugging.

Tools für Profiling und Leistungsmessung

Es gibt mehrere Utilities, mit deren Hilfe Sie die Leistungen Ihres Programms messen und beurteilen können. Diese Tools helfen beim Aufspüren von Engpässen in Ihrem Code -- Stellen, an denen die Leistung einbricht. Mit diesen Tools erhalten Sie auch einen Überblick über die Aufrufstruktur Ihres Programms und können feststellen, welche Funktionen woher und wie oft aufgerufen werden. (Alles, was Sie schon immer über Ihr Programm wissen wollten, aber nicht zu fragen wagten.)

gprof ist ein Profiling-Utility, das Ihnen ausführliche Statistiken zum Ablauf Ihres Programms liefert -- beispielsweise, wie oft eine Funktion aufgerufen wird, von wo, die Laufzeit für jede einzelne Funktion usw.

Wenn Sie gprof auf ein Programm anwenden möchten, müssen Sie beim Kompilieren die Option -pg von gcc benutzen. Damit werden Profiling-Informationen in die Objektdatei geschrieben und Standard-Libraries in die ausführbare Datei eingebunden, die das Profiling aktiviert haben.

Nachdem Sie das Programm mit der Option -pg kompiliert haben, können Sie es ganz normal starten. Wenn es ohne Fehler beendet wird, finden Sie im Arbeitsverzeichnis des Programms die Datei gmon.out vor. Sie enthält die Profiling-Informationen für diesen Programmlauf und kann mit gprof benutzt werden, um die Statistiken darzustellen.

Als Beispiel wollen wir ein Programm namens getstat benutzen, das statistische Informationen zu einer Bilddatei erstellt. Wir kompilieren also getstat mit der Option -pg und rufen es auf:

papaya$ getstat image11.pgm > stats.dat 
papaya$ ls -l gmon.out 
-rw-------   1 mdw      mdw         54448 Feb  5 17:00 gmon.out
papaya$

Die Profiling-Informationen wurden tatsächlich in gmon.out abgelegt.

Wir rufen anschließend gprof mit dem Namen der ausführbaren Datei und der Datei gmon.out auf:

papaya$ gprof getstat gmon.out

Wenn Sie den Namen der Profiling-Datei nicht angeben, wird gprof nach dem Namen gmon.out suchen; wenn Sie auch den Namen der ausführbaren Datei nicht angeben, nimmt gprof an, daß a.out gemeint ist.

gprof gibt ziemlich ausführliche Meldungen aus; vielleicht sollten Sie die Ausgabe in eine Datei schreiben lassen oder durch einen Pager leiten. Die Ausgabe besteht aus zwei Teilen. Der erste Teil ist ein »flaches Profil« mit einem einzeiligen Eintrag zu jeder Funktion. Dieser Eintrag enthält die Zeit, die in der Funktion verbracht wurde, als Prozentwert, die absolute Zeit (in Sekunden) für die Ausführung dieser Funktion, die Anzahl der Aufrufe dieser Funktion und weitere Informationen. Ein Beispiel:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total          
 time   seconds   seconds    calls  ms/call  ms/call  name   
 45.11     27.49    27.49       41   670.51   903.13  GetComponent
 16.25     37.40     9.91                             mcount
 10.72     43.93     6.54  1811863     0.00     0.00  Push
 10.33     50.23     6.30  1811863     0.00     0.00  Pop
  5.87     53.81     3.58       40    89.50   247.06  stackstats
  4.92     56.81     3.00  1811863     0.00     0.00  TrimNeighbors

Falls in der Ausgabe einige der Felder leer bleiben, war gprof nicht in der Lage, genauere Werte zu einer Funktion zu ermitteln. Das liegt meistens daran, daß Teile des Programmcodes nicht mit der Option -pg kompiliert wurden -- wenn Sie beispielsweise Routinen außerhalb der Standard-Libraries aufrufen, die nicht mit -pg kompiliert wurden, kann gprof nicht allzu viel über solche Routinen in Erfahrung bringen. In der obenstehenden Ausgabe läßt sich erkennen, daß mcount wahrscheinlich nicht für das Profiling kompiliert wurde.

Wir können auch ablesen, daß insgesamt 45,11% der Zeit in der Funktion GetComponent verbracht wurden -- immerhin 27,49 Sekunden. Liegt das daran, daß GetComponent schrecklich ineffizient arbeitet, oder hat GetComponent viele andere langsame Funktionen aufgerufen? Die Funktionen Push und Pop sind während der Programmausführung sehr, sehr oft aufgerufen worden -- haben wir damit die Schuldigen gefunden? (4)

Bei dieser Frage kann uns der zweite Teil der gprof -Ausgabe weiterhelfen. Er enthält einen detaillierten »Aufruf-Graphen«, aus dem Sie ablesen können, welche Funktionen andere Funktionen aufgerufen haben und wie oft. Ein Beispiel:

index % time    self  children    called     name
                                                 <spontaneous>
[1]     92.7    0.00   47.30                 start [1]
                0.01   47.29       1/1           main [2]
                0.00    0.00       1/2           on_exit [53]
                0.00    0.00       1/1           exit [172]

In der ersten Spalte des Aufruf-Graphen finden wir den Index -- eine eindeutige Nummer, die jeder Funktion zugeordnet wird; mit Hilfe des Index können Sie andere Funktionen im Graphen finden. In unserem Beispiel wird die erste Funktion, start , beim Programmstart implizit aufgerufen. start hat einschließlich seiner Kindprozesse 92,7% der Gesamtlaufzeit in Anspruch genommen (47,3 Sekunden), aber selbst nur sehr wenig Laufzeit verbraucht. Das liegt daran, daß start allen anderen Funktionen im Programm, einschließlich main , übergeordnet ist -- deshalb ist es nicht ungewöhnlich, daß start mit seinen Ablegern soviel Programmlaufzeit beansprucht.

Der Aufruf-Graph stellt in der Regel zu jeder Funktion die Eltern- ebenso wie die Kindfunktionen dar. In unserem Beispiel können wir ablesen, daß start die Funktionen main , on_exit und exit aufgerufen hat (unter der Zeile für start ). Allerdings gibt es keine Elternfunktion, die normalerweiser über start erscheinen würde -- statt dessen finden wir dort das ominöse <spontaneous>. Das bedeutet, daß gprof nicht in der Lage war, die Elternfunktion zu start zu ermitteln -- wahrscheinlich, weil start nicht aus dem Programm heraus aufgerufen, sondern vom Betriebssystem angestoßen wurde.

Gehen wir weiter zu der Funktion, die wir im Verdacht haben, GetComponent . Dort sehen wir:

index % time    self  children    called     name
                0.67    0.23       1/41          GetFirstComponent [12]
               26.82    9.30      40/41          GetNextComponent [5]
[4]     72.6   27.49    9.54      41         GetComponent [4]
                6.54    0.00 1811863/1811863     Push [7]
                3.00    0.00 1811863/1811863     TrimNeighbors [9]
                0.00    0.00       1/1           InitStack [54]

Die Elternfunktionen von GetComponent sind GetFirstComponent und GetNextComponent , und seine Kindfunktionen sind Push , TrimNeighbors und InitStack . Wie wir sehen, wurde GetComponent 41 Mal aufgerufen -- einmal von GetFirstComponent und 40 mal von GetNextComponent . In der Ausgabe von gprof finden Sie Hinweise, die weitere Details zum Bericht liefern.

GetComponent selbst läuft für mehr als 27,49 Sekunden -- nur 9,54 Sekunden werden gebraucht, um die Kinder von GetComponent auszuführen (einschließlich der vielen Aufrufe von Push und TrimNeighbors !). Es sieht also so aus, als ob GetComponent und eventuell seine Elternfunktion GetNextComponent überarbeitet werden sollten. Die häufig aufgerufene Funktion Push ist nicht die einzige Ursache des Problems.

gprof ist auch in der Lage, rekursive Aufrufe sowie sich immer wieder gegenseitig aufrufende Funktionen zu verfolgen und die Zeit für jeden Aufruf zu registrieren. Natürlich müssen alle Bestandteile des Programmcodes, die einem Profiling unterworfen werden sollen, mit der Option -pg kompiliert werden, damit gprof mit Erfolg benutzt werden kann. Außerdem sollten Sie das Programm kennen, das Sie untersuchen möchten -- gprof kann nur eine gewisse Menge an Informationen ermitteln. Es liegt am Programmierer, ineffizienten Code zu verbessern.

Eine abschließende Bemerkung zu gprof : Wenn Sie es auf ein Programm ansetzen, das nur wenige Funktionen aufruft und sehr schnell läuft, erhalten Sie eventuell keine aussagekräftigen Ergebnisse. Die Laufzeiten werden meist in recht großen Zeiteinheiten gemessen -- etwa in Hundertstelsekunden -- und wenn viele Funktionen in Ihrem Programm schneller sind, kann gprof die Laufzeiten der einzelnen Funktionen nicht mehr unterscheiden (und rundet dann auf die nächste Hundertstelsekunde auf oder ab). Um brauchbare Profiling-Informationen zu erhalten, müssen Sie Ihr Programm vielleicht unter ungewöhnlichen Umständen laufen lassen -- etwa indem Sie es mit besonders vielen Daten füttern, wie wir das in unserem Beispiel getan haben.

Falls gprof mehr leistet, als Sie benötigen, können Sie mit dem Programm calls eine Baumstruktur aller Funktionsaufrufe in Ihrem C-Quellcode erstellen. Diese Struktur können Sie entweder zu einem Index aller aufgerufenen Funktionen oder zu einer hierarchischen Darstellung der Programmstruktur weiterverarbeiten.

Die Benutzung von calls ist sehr einfach. Sie geben dem Programm einfach die Namen der Quelldateien mit, die es analysieren soll, und schon wird die Aufrufstruktur angezeigt. Ein Beispiel:

papaya$ calls scan.c 
    1   level1 [scan.c]
    2           getid [scan.c]
    3                   getc
    4                   eatwhite [scan.c]
    5                           getc
    6                           ungetc
    7                   strcmp
    8           eatwhite [see line 4]
    9           balance [scan.c]
   10                   eatwhite [see line 4]

calls ist so voreingestellt, daß es auf jeder Ebene der Baumstruktur nur ein Vorkommen jeder aufgerufenen Funktion auflistet (wenn also printf innerhalb einer bestimmten Funktion fünf Mal aufgerufen wird, erscheint es nur einmal). Mit dem Schalter -a werden alle Vorkommen aufgelistet. Es gibt weitere Optionen zu calls ; mit calls -h erhalten Sie eine Zusammenfassung.

strace benutzen

strace ist ein Tool, das die Systemaufrufe anzeigt, die ein laufendes Programm durchführt. Das kann bei der Analyse eines Programms zur Laufzeit eine große Hilfe sein -- allerdings erfordert es einige Kenntnisse der Programmierung auf der Ebene der Systemaufrufe. Wenn Sie beispielsweise die Library-Routine printf in einem Programm benutzen, wird strace nur Informationen zum darunterliegenden Systemaufruf write anzeigen, wenn dieser ausgeführt wird. Die Ausgaben von strace können ziemlich umfangreich werden -- d.h. in einem Programm finden viele Systemaufrufe statt, deren sich der Programmierer vielleicht nicht bewußt ist. Trotzdem ist strace ein nützliches Tool, um schnell die Ursache eines Programmabsturzes oder anderer Merkwürdigkeiten zu ermitteln.

Wir benutzen noch einmal das »Hello, World!«-Programm vom Anfang des Kapitels. Wenn wir strace auf die ausführbare Datei ansetzen, erhalten wir:

papaya$ strace hello 
uselib("/lib/ld.so") = 0
open("/etc/ld.so.conf", RDONLY, 0) = 3
fstat(3, [dev 3 2 ino 4143 nlnks 1 ...]) = 0
read(3, "/usr/local/lib\n/usr/X386/lib\n", 29) = 29
close(3) = 0
access("/usr/local/lib/libc.so.4", 0) = -1 (No such file or directory)
access("/usr/X386/lib/libc.so.4", 0) = -1 (No such file or directory)
access("/usr/lib/libc.so.4", 0) = -1 (No such file or directory)
access("/lib/libc.so.4", 0) = 0
uselib("/lib/libc.so.4") = 0
munmap(0x62f00000, , 16384, ) = 0
brk(0) = 0x2000
fstat(1, [dev 3 2 ino 24040 nlnks 1 ...]) = 0
brk(5000) = 0x5000
brk(6000) = 0x6000
ioctl(1, TCGETS, 0xbffff80c) = 0
write(1, "Hello, World!\n", 14) = 14
Hello, World!
exit(0) = ?
papaya$

Das ist wahrscheinlich viel mehr, als Sie von einem kleinen Programm erwartet hatten. Wir wollen die Ausgabe Schritt für Schritt durchgehen und dabei erklären, was passiert.

Der erste uselib -Aufruf ist in die ausführbare Datei eingebettet; er lädt die ld.so -Library, die von Programmen benutzt wird, die mit Shared Libraries gebunden werden. Der Systemaufruf open öffnet die Konfigurationsdatei ld.so.conf , die anschließend gelesen ( read ) und wieder geschlossen ( close ) wird. Die vier Aufrufe von access sind Versuche, das Shared Library-Image libc.so.4 zu finden, mit dem das Programm gebunden wurde. Beim vierten Versuch (für jedes Shared Library-Image wird ein »Library-Path« durchsucht), wird die Datei in /lib/libc.so.4 gefunden und dann mit einem uselib -Aufruf geladen.

Die Aufrufe von munmap (das den memory-mapped Teil einer Datei ausblendet) und brk (das auf dem Stapel Speicher alloziert) setzen die Speicherverwaltung des Prozesses auf. ioctl ist das Resultat eines Aufrufs der Bibliothek tcgetattr , die die Terminaleigenschaften ermittelt, bevor an das Terminal geschrieben wird. Zum Schluß gibt der Aufruf von write unsere freundliche Nachricht an das Terminal aus, und exit beendet das Programm.

strace schickt seine Ausgaben an die Standardfehlerausgabe; Sie haben also die Möglichkeit, diese Meldungen getrennt von den Ausgaben des Programms selbst (die meist an die Standardausgabe gehen) in eine Datei zu schreiben. Wie Sie sehen, gibt strace nicht nur die Namen der Systemaufrufe aus, sondern auch ihre Parameter (wenn möglich in Form von wohlbekannten Namen von Konstanten, statt in Form von Zahlenwerten) und die Rückgabewerte.

make und imake

Wir haben bereits make vorgestellt, den Projektmanager, mit dem u.a. ganze Programmprojekte kompiliert werden. Ein Problem mit make ist, daß die Makefiles nicht ganz einfach zu erstellen sind. Wenn große Projekte anstehen, kann es langweilig werden, ein Makefile zu schreiben, das alle möglichen Quelldateien berücksichtigt. Selbst mit den internen Voreinstellungen von make bleibt häufig mehr Arbeit zu tun, als das der Fall sein sollte.

Die Benutzung von imake , einer Erweiterung zu make , die den C-Präprozessor verwendet, ist eine mögliche Lösung des Problems. imake erzeugt einfach Makefiles -- Sie schreiben ein Imakefile, das imake in ein brauchbares Makefile umwandelt. imake wird von Programmen in der X Window System-Distribution benutzt, kann aber nicht nur mit X-Anwendungen eingesetzt werden.

Wir wollen gleich an dieser Stelle darauf hinweisen, daß imake das Erstellen von Makefiles vereinfachen kann; insbesondere, wenn C-Programme kompiliert werden. Allerdings ist im allgemeinen make für diese Aufgabe viel eher geeignet als imake . So können Sie make beispielsweise zusammen mit groff oder TEX dazu bringen, Schriftstücke automatisch zu formatieren. In diesem Fall sind Sie auf die Flexibilität von make angewiesen, und imake ist vielleicht nicht die beste Lösung.

Wir zeigen Ihnen hier ein Beispiel für ein Imakefile, das die beiden Programme laplacian und getstat kompiliert. Am Anfang des Imakefiles stehen Optionen, die die gesamte Kompilierung betreffen ( imake hat seine eigenen Voreinstellungen hierfür, aber die sind nicht immer hilfreich). Anschließend werden für jedes zu kompilierende Programm Variablen definiert, und mit den imake -Makros AllTarget und NormalProgramTarget werden Makefile-Regeln für die Kompilierung dieser Programme aufgestellt.

# Linker-Optionen:
LDOPTIONS = -L/usr/local/lib -L../lib
# Benutze diesen C-Compiler:
CC = gcc
# Benutze diese Flags mit gcc:
CFLAGS = -I. -I$(HOME)/include -g
# Binde mit diesen lokalen und System-Libraries:
LOCAL_LIBRARIES = -lvistuff
SYS_LIBRARIES = -lm

# Bestimme die Quelldateien in der Variablen SRCS und die entsprechenden
# Objektdateien in der Variablen LAP_OBJS:
SRCS = laplacian.c laplacian-main.c
LAP_OBJS = laplacian.o laplacian-main.o

# Stelle Regeln für laplacian auf:
AllTarget(laplacian)
NormalProgramTarget(laplacian,$(LAP_OBJS),,$(LOCAL_LIBRARIES),$(SYS_LIBRARIES))

# Dasselbe für getstat. Beachten Sie, daß SRCS für jedes Ziel
neu
# definiert werden kann, aber LAP_OBJS nicht; wir benutzen deshalb einen
# eindeutigen Namen für jedes Ziel.
SRCS = getstat.c getstat-main.c component.c
GS_OBJS = getstat.o getstat-main.o component.o

AllTarget(getstat)
NormalProgramTarget(getstat,$(GS_OBJS),,$(LOCAL_LIBRARIES),$(SYS_LIBRARIES))

Beachten Sie, daß wir für jedes Ziel eine andere Variable für die Objektdatei benutzen müssen, während SRCS immer wieder neu definiert werden kann.

Geben Sie den Befehl xmkmf ein, um aus dem Imakefile ein Makefile zu erstellen. xmkmf ruft einfach imake mit den für die Übersetzung geeigneten Optionen auf und benutzt dabei die Standardmakros von imake (wie AllTarget und NormalProgramTarget). Anschließend können Sie mit make das Programm kompilieren.

papaya$ xmkmf 
mv Makefile Makefile.bak
imake -DUseInstalled -I/usr/X386/lib/X11/config
papaya$

Falls Sie Ihre eigenen imake -Makros einsetzen möchten, können Sie imake von Hand mit den entsprechenden Optionen aufrufen. In den Manual-Pages zu imake und xmkmf finden Sie weitere Informationen. Das Buch Software Portability with imake von Paul DuBois bietet ebenfalls eine Beschreibung dieses Systems.

Für den Fall, daß Ihnen imake zu kompliziert vorkommt, gibt es andere »Makefile-Macher« -- etwa ICmake , das Makefiles mit Hilfe einer Makrosprache erstellt, die sehr an C erinnert.

Checker benutzen

Checker ersetzt die verschiedenen Routinen, die von C-Programmen für die Speicherverwaltung benutzt werden (wie z.B. malloc , realloc und free ). Checker hat die besseren Prozeduren zur Verwaltung des Speichers sowie Code, der unzulässige Speicherzugriffe und häufig gemachte Fehler entdeckt (wie z.B. den Versuch, einen Speicherbereich mehr als einmal freizugeben). Wenn Ihr Programm kritische Speicherzugriffe durchführen möchte, wird Checker ausführliche Fehlermeldungen ausgeben und Ihnen damit helfen, Segmentierungsfehler in Ihrem Programm abzufangen, bevor sie auftreten. Checker ist außerdem in der Lage, Speicherverluste zu verhindern -- beispielsweise solche Stellen im Code, an denen Speicher mit malloc zugewiesen und anschließend nicht mit free wieder freigegeben wird.

Checker ist mehr als nur ein Ersatz für malloc und Co. Es fügt auch Code in Ihr Programm ein, der alle Lese- und Schreibzugriffe auf den Speicher verifiziert. Es ist äußerst robust und deshalb etwas langsamer als die üblichen malloc -Routinen. Checker ist für den Einsatz während der Programmentwicklung und -tests gedacht -- sobald alle potentiellen Speicherfehler beseitigt sind, können Sie Ihr Programm mit den Standardbibliotheken binden.

Lassen Sie uns folgendes Beispielprogramm betrachten, das Speicher zuordnet und dann verschiedene haarige Dinge damit anzustellen versucht:

#include <malloc.h>
int main() {
  char *thememory, ch;

  thememory=(char *)malloc(10*sizeof(char));

  ch=thememory[1];     /* Attempt to read uninitialized memory */
  thememory[12]=' ';   /* Attempt to write after the block */
  ch=thememory[-2];    /* Attempt to read before the block */
}

Wir kompilieren dieses Programm einfach mit der Option -lchecker , so daß es mit den Checker-Libraries gebunden wird. Nach dem Aufruf erhalten wir (u.a.) folgende Fehlermeldungen:

From Checker:
        Memory access error
        When Reading at address 0x10033
        inside the heap
        1 bytes after the begin of the block
From Checker:
        Memory access error
        When Writing at address 0x1003e
        inside the heap
        2 bytes after the end of the block
From Checker:
        Memory access error
        When Reading at address 0x10030
        inside the heap
        2 bytes before the begin of the block

Zu jedem Speicherfehler gibt Checker eine Meldung aus und teilt uns mit, was passiert ist. Die vollständigen Fehlermeldungen von Checker enthalten auch Informationen darüber, wo das Programm ausgeführt wird und wo der Speicherblock vergeben wurde. Auf Wunsch können Sie noch mehr Informationen aus Checker herauskitzeln -- zusammen mit einem Debugger wie gdb lassen sich Problemstellen dann leicht finden. (5)

Checker enthält auch einen Garbage-Collector und -Detector, den Sie aus Ihrem Programm heraus aufrufen können. Kurz gesagt: Der Garbage-Detector informiert Sie über alle Speicherverluste -- Stellen, an denen eine Funktion mit malloc Speicher zugewiesen hat, den sie vor der Rückkehr nicht mit free wieder freigegeben hat. Der Garbage-Collector untersucht den Stapel und bereinigt die Folgen solcher Speicherverluste. Sie können Garbage-Collector und -Detector auch von Hand aufrufen, während Sie Ihr Programm innerhalb von gdb laufen lassen (weil gdb den direkten Aufruf von Funktionen zur Laufzeit unterstützt).

Tools für die Erstellung von Benutzerschnittstellen

Es gibt eine Reihe von Anwendungen und Bibliotheken, die auf einfache Weise die Erstellung von Benutzerschnittstellen für Ihre Programme unter dem X Window System unterstützen. Wenn Sie sich nicht mit der Komplexität der X-Programmierschnittstelle auseinandersetzen möchten, ist eines dieser einfachen Tools zum Erstellen einer Benutzerschnittstelle vielleicht genau das, was Sie suchen. Es gibt auch Tools, die die Erstellung von textbasierten Schnittstellen für solche Programme unterstützen, die nicht unter X laufen.

Die klassische X-Programmierung wollte so allgemein wie möglich bleiben und nur ein absolutes Minimum an Beschränkungen und Voraussetzungen für das Interface vorschreiben. Das gibt den Programmierern die Möglichkeit, ihre eigenen Schnittstellen »von Grund auf« zu entwerfen, weil die zugrundeliegenden X-Bibliotheken vorab keinerlei Voraussetzungen an das Interface stellen. Das X-Toolkit Intrinsics (Xt) stellt einige wenige Interface-Widgets (etwa einfache Schaltflächen, Laufleisten und Ähnliches) zur Verfügung; außerdem ist ein generelles Interface zum Schreiben eigener Widgets vorhanden. Unglücklicherweise steht den Programmierern, die lieber einen Satz vorgefertigter Interface-Routinen benutzen würden, eventuell eine Menge Arbeit ins Haus. Eine Reihe von Xt-Widget-Sets und Bibliotheken stehen unter Linux zur Verfügung, die alle dazu beitragen, daß die Benutzerschnittstelle einfacher zu erstellen ist.

Zusätzlich gibt es von verschiedenen Quellen die kommerzielle Motif-Library und das Widget-Set in einer Einzelplatzversion zu einem günstigen Preis. Außerdem existiert die the XView-Library mit Widget-Interface als eine weitere Alternative zur Erstellung von Schnittstellen mit Xt. XView und Motif sind zwei Sammlungen von X-basierten Programmbibliotheken, die in mancher Hinsicht einfacher zu programmieren sind als das X-Toolkit Intrinsics. Es gibt viele Anwendungsprogramme, die Motif und XView nutzen -- etwa XVhelp (ein System, mit dem Sie eine interaktive Hypertext-Hilfefunktion für Ihr Programm erzeugen). Binärcode, der mit Motif statisch gebunden wurde, darf ohne Einschränkungen weitergegeben werden und kann so auch von Leuten benutzt werden, die kein eigenes Motif haben.

Zu den Widget-Sets und Interface-Libraries für X gehören:

Viele Leute sind der Meinung, daß die Athena-Widgets zu schlicht aussehen. Xaw3D ist vollständig kompatibel zu den Standard-Athena-Widgets und kann sogar benutzt werden, um die Athena-Libraries auf Ihrem System zu ersetzen; dadurch bekommen alle Programme, die Athena benutzen, ein modernes Aussehen. Xaw3D enthält zusätzlich ein paar Widgets, die in der Athena-Sammlung nicht enthalten sind; etwa ein Layout-Widget mit einer TEX-ähnlichen Schnittstelle zur Bestimmung der Position eines Kind-Widgets.

Ein anderes ausgezeichnetes Paket zur Erstellung von X-basierten Anwendungen ist wxWindows, eine Sammlung von Bibliotheken und Tools, die das Erstellen von Anwendungen sowohl für X als auch für Microsoft Windows 3.1 (unter MS-DOS) unterstützen. Mit wxWindows können Sie denselben C++-Code benutzen, um Anwendungen für XView, Motif und Microsoft Windows zu erstellen; gleichzeitig werden Windows NT- und textbasierte Schnittstellen unterstützt. Zu den vielen Features von wxWindows gehören Menübalken, Werkzeugleisten, Stifte und Pinsel, Fonts und Icons, die Erzeugung von Encapsulated PostScript (zum Ausdrucken), eine Hypertexthilfe, vollständige Dokumentation und noch mehr. Sie finden wxWindows in vielen Linux-Archiven, und Sie brauchen entweder Motif oder XView, um unter Linux X-basierte Anwendungen zu entwickeln.

Viele Programmierer sind der Meinung, daß die Erzeugung von Benutzerschnittstellen, selbst mit einer vollständigen Sammlung von Widgets und C-Routinen, einigen Aufwand verursacht und ziemlich schwierig sein kann. Hier muß man Flexibilität gegen einfache Programmierung abwägen -- je einfacher die Schnittstelle erzeugt werden kann, desto weniger Einfluß kann der Programmierer darauf nehmen. Viele Programmierer begnügen sich mit vorgefertigten Widgets, so daß die verlorengegangene Flexibilität keine Rolle mehr spielt.

Eines der Probleme bei der Erzeugung von Schnittstellen und der Programmierung unter X ist die Tatsache, daß es sehr schwierig ist, für die am häufigsten benutzten Elemente einer Benutzerschnittstelle ein allgemeingültiges, einfaches Programmiermodell zu finden. Ein Beispiel: Viele Programme benutzen solche Elemente wie Schaltflächen, Dialogboxen, Pull-down-Menüs usw. -- aber fast jedes Programm benutzt diese »Widgets« in einem anderen Kontext. Indem die Tools das Erstellen von grafischen Schnittstellen vereinfachen, machen Sie in der Regel auch Annahmen dazu, was Sie haben wollen. Auch dazu ein Beispiel: Es ist kein Problem, zu bestimmen, daß ein Klick auf eine Schaltfläche innerhalb Ihres Programms eine bestimmte Prozedur ausführen soll; was aber passiert, wenn Sie dieser Schaltfläche eine spezielle Aktion zuordnen wollen, die in der Programmierschnittstelle nicht vorgesehen ist? Beispielsweise könnte ein Klick auf diese Schaltfläche unterschiedliche Reaktionen hervorrufen -- je nachdem, ob mit der rechten oder der linken Maustaste geklickt wird. Wenn das System zum Erzeugen der Schnittstelle ein solches Maß an Flexibilität nicht vorgesehen hat, ist es für die Programmierer kaum zu gebrauchen, die ein vielseitiges, speziell angepaßtes Interface benötigen.

Die Programmierschnittstelle Tcl/Tk, die wir weiter oben in diesem Kapitel beschrieben haben, wird immer beliebter. Das liegt zum Teil daran, daß sie so einfach zu benutzen ist und ein hohes Maß an Flexibilität bietet. Weil Tcl- und Tk-Routinen sowohl von interpretierten »Skripts« als auch aus einem C-Programm heraus aufgerufen werden können, ist es nicht schwierig, die Schnittstellenfunktionen dieser Sprache und dieses Toolkits mit Funktionen im Programm zu verknüpfen. Die Arbeit mit Tcl und Tk ist insgesamt weniger anspruchsvoll, als die direkte Programmierung mit Xlib und Xt zu erlernen (zusammen mit den unzähligen Widget-Sets).

TclMotif, eine Version von Tcl, die mit dem beliebten Widget-Set von Motif gebunden wurde, ist ebenfalls für Linux erhältlich. Man sagt den Motif-Widgets nach, daß sie einfach zu programmieren und angenehm zu benutzen sind. Der Vorteil von TclMotif ist, daß die ausführbare Datei frei verteilt werden darf, obwohl Motif selbst ein kommerzielles Produkt ist. Sie müssen also nicht Motif besitzen, um TclMotif zu benutzen. TclMotif gestattet es, daß Sie Programme schreiben, die über die Tcl-Schnittstelle die Widgets und Routinen von Motif benutzen. Eine statisch gebundene ausführbare Datei finden Sie auf einer Reihe von Linux-FTP-Rechnern und bei anderen Quellen. Falls Sie aus irgendeinem Grund vorhaben, TclMotif selbst neu zu kompilieren, müßten Sie dazu erst Motif besitzen.

Wafe ist eine andere Version von Tcl/Tk, die auch die Athena-Widgets sowie diverse andere Tools enthält, die die Programmierung vereinfachen. Wenn Sie bisher Xt mit den Athena-Widgets programmiert haben, aber jetzt auf Tcl und Tk umsteigen möchten, haben Sie mit Wafe einen guten Startpunkt gefunden.

Mit Tcl und Tk können Sie um ein existierendes Programm herum eine komplette X-basierte Schnittstelle mit Fenstern, Schaltflächen, Menüs, Laufleisten und anderen Dingen erzeugen. Sie können entweder von einem Tcl-Skript aus (wie im Abschnitt » Programmieren mit Tcl und Tk « beschrieben) oder aus einem C-Programm heraus auf die Schnittstelle zugreifen.

Ein anderes Tools zum Erstellen von Schnittstellen, das große Ähnlichkeiten mit Tcl und Tk aufweist, ist xtpanel . xtpanel soll in erster Linie einen X-»Umschlag« (wrapper) um eine existierende textbasierte Anwendung herumlegen. Mit xtpanel können Sie ein Fenster mit verschiedenen Bereichen, Ausschnitten zum Editieren von Texten, Schaltflächen, Laufleisten usw. definieren; die Aktionen dieser Widgets verknüpfen Sie dann mit bestimmten Fähigkeiten des Programms. Man könnte beispielsweise mit xtpanel nach dem Vorbild von xxgdb eine X-basierte Schnittstelle zum Debugger gdb erzeugen. Sie könnten darin einen »step«-Button definieren, der nach dem Anklicken den Befehl step an die eigentliche gdb -Schnittstelle schickt. Man könnte ein Textfenster definieren, in dem der gdb in der üblichen Weise benutzt würde. Natürlich wäre es schwierig, mit einem wenig spezialisierten Programm wie xtpanel eine komplexere Aufgabe wie die Erzeugung eines Quellcodefensters zu bewerkstelligen.

Wenn Sie ein nettes textbasiertes Interface zu einem Programm suchen, haben Sie mehrere Möglichkeiten. Die getline -Library von GNU besteht aus einer Sammlung von Routinen, die weitergehende Möglichkeiten zum Editieren der Befehlszeile und zum Anzeigen von Aufforderungen an den Benutzer enthält; auch das Wiederaufrufen bereits abgeschickter Befehle (command history) und andere Fähigkeiten, die von vielen Programmen genutzt werden, sind enthalten. So lesen z.B. sowohl bash als auch gdb die Eingaben des Benutzers mit Hilfe der getline -Bibliothek.

getline schafft auch die Möglichkeiten zum Editieren der Befehlszeile mit vi - und Emacs-ähnlichen Befehlen, die man in bash und ähnlichen Programmen findet. (Wir beschreiben das Editieren der Befehlszeile unter bash im Abschnitt » Tips für schnelle Tipper « in Kapitel 3 .)

Eine andere Möglichkeit besteht darin, eine Sammlung von Emacs-Interface-Routinen für Ihr Programm zu schreiben. Ein Beispiel dafür ist die Schnittstelle zwischen gdb und Emacs, die innerhalb von Emacs mehrere Fenster öffnet, Sondertasten definiert usw. (Wir besprechen dieses Interface im Abschnitt » Emacs und gdb « .) Im gdb -Code mußten keinerlei Änderungen vorgenommen werden, um diese Schnittstelle zu implementieren -- in der Emacs-Library-Datei gdb.el können Sie nachschauen, wie das bewerkstelligt wurde. Emacs erlaubt den Aufruf eines Unterprogramms innerhalb eines Textpuffers, und es kennt viele Routinen für die Analyse und Bearbeitung von Text in diesem Puffer. Ein Beispiel: Innerhalb seiner gdb -Schnittstelle fängt Emacs die Ausgaben eines Quellcodelistings von gdb ab und übergibt das Listing an einen Befehl, der die aktuelle Codezeile in einem anderen Fenster anzeigt. Routinen in Emacs-LISP verarbeiten die Ausgabe von gdb und führen anschließend -- abhängig von der Ausgabe -- bestimmte Aktionen durch.

Der Vorteil eines Zusammenspiels zwischen Emacs und textbasierten Programmen ist, daß Emacs selbst eine ausgefeilte und anpassungsfähige Benutzerschnittstelle darstellt. Der Benutzer kann problemlos Tasten und Befehle nach seinen Wünschen neu belegen -- Sie müssen diese Möglichkeiten zur Konfiguration nicht selbst schaffen. Solange die Textschnittstelle des Programms in der Lage ist, mit Emacs zusammenzuarbeiten, läßt sich das auf einfache Weise einrichten. Außerdem ziehen viele Benutzer es vor, beinahe alles innerhalb von Emacs zu tun -- das Lesen von E-Mail und News ebenso wie das Kompilieren und Debuggen von Programmen. Wenn Sie Ihr Programm mit einem Emacs-Front-End versehen, erleichtern Sie diesen Leuten die Benutzung des Programms. Ihr Programm wird auch in der Lage sein, mit anderen Programmen zu kooperieren, die ebenfalls unter Emacs laufen -- beispielsweise kann Text zwischen verschiedenen Emacs-Textpuffern problemlos hin- und herkopiert werden. Wenn Sie wollen, können Sie auch komplette Programme in Emacs-LISP schreiben.

Tools für die Versionskontrolle

Das Revision Control System (RCS; System zur Versionskontrolle) ist nach Linux portiert worden. Es handelt sich dabei um eine Sammlung von Programmen, die eine »Bibliothek« von Dateien unterhalten, in denen Versionswechsel vermerkt werden. RCS gestattet außerdem das Sperren von Quellcodedateien (wenn mehrere Leute an einem Projekt arbeiten) und merkt sich automatisch die Versionsnummern dieser Dateien. Das RCS wird meistens mit Quellcodedateien benutzt, ist aber so flexibel, daß es mit beliebigen Dateiarten angewendet werden kann, von denen mehrere Versionen gepflegt werden müssen.

Wozu braucht man eine Versionskontrolle? Für viele große Projekte ist die Versionskontrolle notwendig, um den Überblick über zahlreiche kleine, komplexe Änderungen am System zu behalten. Wenn Sie beispielsweise versuchen, ein Programm mit 1000 Quelldateien und einigen Dutzend Programmierern zu pflegen, haben Sie ohne so etwas wie RCS kaum eine Chance. Mit RCS können Sie sicherstellen, daß zu einem beliebigen Zeitpunkt nur eine Person eine bestimmte Datei ändern kann; alle Änderungen werden, zusammen mit einem Hinweis auf die Art derselben, schriftlich festgehalten.

RCS basiert auf dem Konzept einer RCS-Datei -- das ist eine Datei, die wie eine »Bibliothek« funktioniert, in der Quelldateien »eingetragen« und »ausgetragen« werden. Nehmen wir an, daß Sie die Quellcodedatei foo.c mit RCS verwalten möchten. Per Voreinstellung heißt die RCS-Datei dann foo.c,v . Die RCS-Datei enthält die Geschichte aller Versionen dieser Datei, so daß Sie die Möglichkeit haben, beliebige frühere Versionen der Datei zu extrahieren. Zu jeder Version können Sie selbst eine Notiz (log) hinzufügen.

Wenn Sie mit dem RCS eine Datei eintragen, werden die Änderungen in die RCS-Datei aufgenommen und die Originaldatei wird (in der Voreinstellung) gelöscht. Wenn Sie auf die Originaldatei zugreifen möchten, müssen Sie sie aus der RCS-Datei austragen. Wenn Sie gerade eine Datei bearbeiten, möchten Sie in der Regel vermeiden, daß jemand anderes gleichzeitig daran arbeitet. RCS wird die Datei deshalb sperren, wenn sie zum Editieren ausgetragen wurde. Eine gesperrte Datei kann nur von dem Benutzer verändert werden, der sie ausgetragen hat (dies wird mit Hilfe der Dateiberechtigungen erreicht). Sobald Sie mit Ihren Änderungen am Quellcode fertig sind, tragen Sie die Datei wieder ein; anschließend kann jeder andere Mitarbeiter diese Datei wieder austragen, um weiter daran zu arbeiten. Wenn Sie eine Datei im ungesperrten Zustand austragen, gelten diese Einschränkungen nicht; im allgemeinen werden Dateien dann als gesperrt ausgetragen, wenn Sie editiert werden sollen, und sie werden ungesperrt ausgetragen, wenn sie nur gelesen werden (um den Quellcode beispielsweise beim Debuggen vor Augen zu haben).

RCS vermerkt automatisch alle früheren Änderungen in der RCS-Datei und vergibt aufsteigende Versionsnummern an jede neu eingetragene Version. Sie haben auch die Möglichkeit, beim Eintragen einer Datei mit RCS selbst eine Versionsnummer zu vergeben; damit können Sie einen neuen »Versionszweig« anlegen, so daß mehrfach vorhandene Projekte von verschiedenen Versionen derselben Datei abstammen können. Auf diese Weise kann Programmcode in verschiedenen Projekten benutzt werden, ohne daß die Änderungen aus einem Projekt auch in den anderen wirksam werden.

Dazu ein Beispiel: Wir gehen von der Quelldatei foo.c aus, die unser freundliches Programm enthält:

#include <stdio.h>
void main(void) {
  printf("Hello, world!");
}

Als erstes müssen wir die Datei mit dem Befehl ci in das RCS eintragen:

papaya$ ci foo.c 
foo.c,v  <--  foo.c
enter description, terminated with single '.' or end of file:
NOTE: This is NOT the log message!
>> Hello world source code
>> .  
initial revision: 1.1
done
papaya$

Die RCS-Datei foo.c,v wird angelegt und foo.c wird entfernt.

Wenn Sie die Quelldatei weiter bearbeiten möchten, tragen Sie sie mit dem Befehl co wieder aus. Mit:

papaya$ co -l foo.c 
foo.c,v  -->  foo.c
revision 1.1 (locked)
done
papaya$

tragen Sie foo.c (aus foo.c,v ) aus und sperren die Datei. Sie können dann die gesperrte Datei editieren und wieder eintragen. Wenn Sie eine Datei nur zum Lesen austragen möchten (um z.B. make aufzurufen), können Sie den Schalter -l hinter dem Befehl co weglassen und die Datei ungesperrt austragen. Sie können eine Datei nur eintragen, wenn Sie zuvor gesperrt wurde (oder wenn Sie noch nie eingetragen war, wie in unserem Beispiel).

Jetzt haben Sie Gelegenheit, den Quellcode zu ändern und die Datei anschließend wieder einzutragen. Oft werden Sie die Datei permanent ausgetragen lassen und ci nur benutzen, um die letzten Änderungen in der RCS-Datei zu vermerken und die Versionsnummer zu erhöhen. Benutzen Sie für diesen Zweck den Schalter -l mit ci , etwa so:

papaya$ ci -l foo.c 
foo.c,v  <--  foo.c
new revision: 1.2; previous revision: 1.1
enter log message, terminated with single '.' or end of file:
>> Changed printf call 
>> .  
done
papaya$

Damit wird die Datei nach dem Eintragen automatisch in gesperrtem Zustand wieder ausgetragen. Auf diese Weise können Sie Änderungen dokumentieren, wenn Sie der einzige Mitarbeiter an einem Projekt sind.

Wenn Sie RCS häufig benutzen, möchten Sie vielleicht nicht die ganzen unschönen foo.c,v -Dateien in Ihrem Verzeichnis stehen haben. Legen Sie deshalb in Ihrem Projektverzeichnis das Unterverzeichnis RCS an, und ci sowie co werden die RCS-Dateien dort ablegen -- getrennt vom Rest der Quellcodedateien.

RCS hebt außerdem alle vorherigen Versionen Ihrer Datei auf. Wenn Sie beispielsweise eine Datei ändern, und diese Änderung führt zu einem Programmabbruch, möchten Sie vielleicht die letzte Änderung rückgängig machen und mit der vorherigen Version noch einmal beginnen. Sie können zu diesem Zweck mit co die Datei mit einer bestimmten Versionsnummer austragen. Ein Beispiel:

papaya$ co -l1.1 foo.c 
foo.c,v  -->  foo.c
revision 1.1 (locked)
writable foo.c exists; remove it? [ny](n): y 
done
papaya$

Damit tragen Sie die Version 1.1 der Datei foo.c aus. Mit dem Programm rlog können Sie die Versionsgeschichte einer bestimmten Datei einsehen; Sie bekommen die Notizen angezeigt, die Sie mit ci eingegeben haben, sowie weitere Informationen wie das Datum, den Benutzer, der die Version eingetragen hat usw.

Beim Austragen wird RCS automatisch die Informationen aktualisieren, die sich hinter bestimmten »Schlüsselwörtern« verbergen. Wenn Sie beispielsweise den String:

/* &dollar;Header$ */

in die Quellcodedatei schreiben, ersetzt co ihn durch einen ausführlichen Text mit dem Versionsdatum, der Versionsnummer usw. -- etwa so:

/* $Header: /work/linux/hitch/programming/tools/RCS/rcs.tex
       1.2 1994/12/04 15:19:31 mdw Exp mdw $ */

Wir haben die Zeile umbrochen, damit sie auf die Seite paßt; in Wirklichkeit finden Sie alle Informationen in einer Zeile vor.

Es gibt weitere Schlüsselwörter wie &dollar;Author$, &dollar;Date$ und &dollar;Log$ (letzteres enthält die Notizen zu allen Versionen, die in der Quelldatei enthalten sind).

Viele Programmierer schreiben eine feste Zeichenfolge in jede Quellcodedatei, die nach dem Kompilieren benutzt werden kann, um diese Programmversion zu identifizieren. Sie könnten z.B. folgendes in jede Quelltextdatei Ihres Programms schreiben:

static char rcsid[] = "\@(#)&dollar;Header$";

co ersetzt das Schlüsselwort &dollar;Header$ durch einen Text, wie wir ihn etwas weiter oben gezeigt haben. Diese statische Zeichenfolge wird in der ausführbaren Datei erhalten bleiben, und mit dem Befehl what können Sie solche Strings anzeigen lassen. Beispielsweise könnten wir nach dem Kompilieren von foo.c zu der ausführbaren Datei foo folgenden Befehl eingeben:

papaya$ what foo 
foo:
        $Header: /work/linux/hitch/programming/tools/RCS/rcs.tex
                      1.2 1994/12/04 15:19:31 mdw Exp mdw $
papaya$

what sucht die Zeichenfolgen heraus, die mit den Zeichen @(#) beginnen und zeigt sie an. Wenn Sie ein Programm vor sich haben, das aus vielen Quelldateien und Libraries kompiliert wurde, und Sie wissen nicht mehr, wie aktuell die einzelnen Bestandteile sind, können Sie mit what Versionsinformationen zu allen Quelldateien anzeigen, aus denen die ausführbare Datei kompiliert wurde.

Zu RCS gehören einige weitere Programme, darunter auch rcs , das für die Verwaltung von RCS-Dateien benutzt wird. rcs kann z.B. anderen Benutzern die Berechtigung erteilen, Quelldateien aus einer RCS-Datei auszutragen. Sie finden weitere Informationen in den Manual-Pages zu ci, co und rcs.

Dateien patchen

Nehmen wir an, daß Sie versuchen, ein Programm zu pflegen, das regelmäßig aktualisiert wird; das Programm besteht aber aus vielen Quelldateien und es ist einfach nicht machbar, mit jedem Update die kompletten Quelldateien herauszugeben. Die beste Methode, Quellen nach und nach zu aktualisieren, ist patch , ein Programm von Larry Wall, dem Autor von Perl.

patch ist ein Programm, mit dem Sie kontextabhängige Änderungen in einer Datei vornehmen können, um ein Update auf die nächste Version durchzuführen. Sie geben also bei Änderungen im Programm einfach eine Patch-Datei (patch; Flicken) zum Quellcode heraus, die der Benutzer dann mit patch einspielen kann, um die neueste Programmversion zu erhalten. Linus Torvalds gibt z.B. neue Versionen des Linux-Kernels normalerweise in Form von Patch-Dateien heraus (es sei denn, die Änderungen von einer Version zur nächsten waren so umfangreich, daß sich die Distribution des kompletten Quellcodes lohnt).

patch ist netterweise in der Lage, Updates im richtigen Kontext durchzuführen. Wenn Sie also den Quellcode selbst geändert haben, aber trotzdem noch die Änderungen aus der Patch-Datei einspielen möchten, kann patch in der Regel herausfinden, an welcher Stelle der Originaldatei die Änderungen eingespielt werden müssen. Deshalb muß Ihre Version der Originalquellen nicht genau mit der Version übereinstimmen, für die das Patchfile erstellt wurde.

Mit dem Programm diff , das »Kontext-Diffs« (eine kontextsensitive Liste der Unterschiede) zwischen zwei Dateien erzeugen kann, werden die Patch-Dateien erstellt. Wir benutzen wieder den hinreichend bekannten »Hello World«-Quelltext als Beispiel:

/* hello.c version 1.0 by Norbert Ebersol */
#include <stdio.h>

void main() {
 printf("Hello, World!");
 exit(0);
}

Nehmen wir an, Sie wollen diesen Quelltext folgendermaßen ändern:

/* hello.c version 2.0 */
/* (c)1994 Helmut Hohl */
#include <stdio.h>

int main() {
  printf("Hello, Mother Earth!\n");
  return 0;
}

Wenn Sie ein Patchfile erzeugen möchten, das die Originaldatei hello.c auf den aktuellen Stand bringt, geben Sie diff mit der Option -c ein:

papaya$ diff -c hello.c.old hello.c > hello.patch

Damit erzeugen Sie die Patch-Datei hello.patch , in der beschrieben wird, wie die Originaldatei hello.c (in diesem Fall unter hello.c.old abgelegt) in die neue Version umgewandelt wird. Sie können dieses Patchfile an alle Interessenten verteilen, die die Originalversion von »Hello, World« haben; diese können dann mit patch das Update durchführen.

Die Benutzung von patch ist recht einfach; in den meisten Fällen rufen Sie es mit dem Patchfile als Eingabedatei auf:

papaya$ patch < hello.patch 
Hmm...  Looks like a new-style context diff to me...
The text leading up to this was:
--------------------------
|*** hello.c.old        Sun Feb  6 15:30:52 1994
|--- hello.c    Sun Feb  6 15:32:21 1994
--------------------------
Patching file hello.c using Plan A...
Hunk #1 succeeded at 1.
done
papaya$

patch gibt eine Warnung aus, wenn der Patch anscheinend schon einmal eingespielt wurde. Wenn wir versuchen, diese Patch-Datei noch einmal anzuwenden, wird patch fragen, ob es so tun soll, als ob der Schalter -R gesetzt sei -- mit diesem Schalter wird ein Patch rückgängig gemacht. Auf diese Weise lassen sich Patches wieder entfernen, die versehentlich eingespielt wurden. patch hebt die Originalversionen aller aktualisierten Dateien in einer Backup-Datei auf, die in der Regel dateiname ~ genannt wird (der Dateiname mit angehängter Tilde).

Oft werden Sie nicht nur eine einzelne Quelltextdatei patchen wollen, sondern einen ganzen Verzeichnisbaum mit Quellen. Bei patch können Sie mit einem einzigen diff mehrere Dateien aktualisieren. Wir gehen von den beiden Verzeichnisbäumen hello.old und hello aus, in denen der Quellcode der alten bzw. neuen Version des Programms steht. Benutzen Sie diff mit dem Schalter -r , um eine Patch-Datei für den ganzen Verzeichnisbaum zu erzeugen:

papaya$ diff -cr hello.old hello > hello.patch

Wir begeben uns jetzt auf das System, auf dem die Software aktualisiert werden soll. Wenn wir davon ausgehen, daß die Originalquelltexte im Verzeichnis hello stehen, können Sie das Update folgendermaßen einspielen:

papaya$ patch -p0 < hello.patch

Der Schalter -p0 weist patch an, die Pfadnamen der Dateien beizubehalten, die aktualisiert werden sollen ( patch weiß also, daß es im Verzeichnis hello nach dem Quelltext suchen muß). Wenn die Quelltextdatei, die aktualisiert werden soll, in einem anderen Verzeichnis steht als im Patchfile angegeben, müssen Sie eventuell den Schalter -p benutzen.

In der Manual-Page zu patch finden Sie die Details hierzu.

Code einrücken

Falls das Einrücken von Codezeilen nicht Ihre Stärke ist und Sie sich auch nicht mit einem Editor anfreunden können, der den Code während des Tippens einrückt, sollten Sie das Programm indent benutzen, um nach dem Eintippen des Programmcodes denselben wohlformatiert auszudrucken. indent ist ein ziemlich schlauer C-Code-Formatierer, dem Sie mit Hilfe einiger Optionen mitteilen, wie Sie Ihren Quelltext eingerückt haben möchten.

Wir beginnen mit diesem fürchterlich formatierten Quelltext:

double fact (double n) { if (n==1) return 1;
else return (n*fact(n-1)); }
void main () {
printf("Factorial 5 is %f.\n",fact(5));
printf("Factorial 10 is %f.\n",fact(10)); exit (0); }

Wenn wir indent auf diese Quelle ansetzen, erhalten wir das deutlich schönere:

#include <math.h>

double
fact (double n)
{
  if (n == 1)
    return 1;
  else
    return (n * fact (n - 1));
}
void
main ()
{
  printf ("Factorial 5 is %f.\n", fact (5));
  printf ("Factorial 10 is %f.\n", fact (10));
  exit (0);
}

Hier sind nicht nur die Zeilen übersichtlich eingerückt, sondern es sind außerdem Leerstellen um die Operatoren und Funktionsparameter herum eingefügt worden, um die Lesbarkeit zu erhöhen. Sie können auf vielerlei Weise bestimmen, wie die Ausgabe von indent aussehen soll -- für den Fall, daß Ihnen diese Art der Einrückung nicht gefällt, bietet indent noch andere Möglichkeiten.

indent ist außerdem in der Lage, aus einer Quelldatei troff -Code zu erzeugen, den Sie dann ausdrucken oder in ein technisches Dokument einfügen können. Dieser Code wird solche Textauszeichnungen wie kursive Kommentare, fett gesetzte Schlüsselwörter usw. enthalten. Ein Befehl wie:

papaya$ indent -troff foo.c | groff -mindent

erzeugt troff -Code und formatiert diesen mit groff .


Fußnoten

(4)
Das ist in den Programmen des Autors jederzeit möglich!
(5)
Wir haben die Ausgabe editiert, um überflüssige Informationen zu entfernen und das Beispiel überschaubarer zu machen.

Inhaltsverzeichnis Vorherige Abschnitt Nächste Kapitel