Eine kurze Einführung in wichtige Unix-Werkzeuge Shell, Make & Co. Übungen zu Informatik B (Objekt-orientierte Programmierung) Elmar Ludwig Universität Osnabrück Sommersemester 2002 Inhaltsverzeichnis 1 Einführung in Unix-Werkzeuge 2 1.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 1.1.1 Begriffe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 1.2 Terminal-Kontrolle . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 1.3 Unix-Manual Konventionen . . . . . . . . . . . . . . . . . . . . . . . 5 1.4 Typische Kommandos . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 1.5 Shell-Programmierung . . . . . . . . . . . . . . . . . . . . . . . . . . 8 1.5.1 Was ist ein Kommando? . . . . . . . . . . . . . . . . . . . . . 8 1.5.2 Was ist ein Wort? . . . . . . . . . . . . . . . . . . . . . . . . . 8 1.5.3 Textersatz für Variablen . . . . . . . . . . . . . . . . . . . . . 9 1.5.4 Textersatz für Kommandos . . . . . . . . . . . . . . . . . . . . 11 1.5.5 Arithmetik mit Variablen . . . . . . . . . . . . . . . . . . . . . 11 1.5.6 Textersatz für Argumente . . . . . . . . . . . . . . . . . . . . . 12 1.5.7 Dateinamen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 1.5.8 Muster für Dateinamen . . . . . . . . . . . . . . . . . . . . . . 13 1.5.9 Ein-/Ausgabe-Umlenkung . . . . . . . . . . . . . . . . . . . . 13 1.5.10 Zusammengesetzte Kommandos . . . . . . . . . . . . . . . . . 14 1.5.11 Kontrollstrukturen . . . . . . . . . . . . . . . . . . . . . . . . 15 1.5.12 Eingebaute Kommandos . . . . . . . . . . . . . . . . . . . . . 17 Make . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 1.6 1 Kapitel 1 Einführung in Unix-Werkzeuge Dies ist eine kurze Einführung in die wichtigsten Unix-Werkzeuge, die zur Programmentwicklung oder zur Bearbeitung von Textdokumenten nützlich sind. Die Übersicht hier ist in vielen Bereichen unvollständig und vereinfacht, und ist daher als Referenzhandbuch nur sehr eingeschränkt zu empfehlen. Teile des Textes sind aus [ATS 94] übernommen. Ein gutes Nachschlagewerk ist [Kernighan 84]. 1.1 Motivation Eine scheinbar einfache Problemstellung: Wie wandle ich alle GIF-Bilder in meinem Heimatkatalog in das JPEG-Format um? Lösung 1: Schreibe ein Programm zur Lösung dieser Speziellen Aufgabe. Was ist, wenn nicht alle Bilder interessant sind? Was ist, wenn ich PNG-Format haben möchte? Was ist, wenn die Bilder auch noch verkleiner werden sollen? Lösung 2: Stecke fertige „Programm-Komponenten“ aus einem „Baukasten“ so zusammen, daß sie das Problem lösen. Welche „Bausteine“ braucht man dafür? – Finden und auswählen von Dateinamen (find) – Erzeugung eines Namens für die Ausgabedatei (sed) – Umwandeln eines Bildes in ein anderes Format (convert) Was mache ich, wenn es keinen fertigen „Baustein“ gibt? 2 Lösung 2 ist natürlich wesentlich flexibler: Bausteine können nahezu beliebig kombiniert werden. Einfache Programm-Komponenten sind leichter zu implementieren, zu testen und wiederzuverwenden. Ein Werkzeug sollte eine kleine, klar definierte Aufgabe optimal erfüllen. Eine Lösung könnte dann z.B. folgendermaßen aussehen: for file in $(find /home/elmar -name "*.gif") do output=$(echo $file | sed s/gif$/jpeg/) convert $file $output done 1.1.1 Begriffe Teminal Gerät zur Ein- und Ausgabe von Text an einem Computer, heute meistens in Software emuliert (Terminal-Emulation). Kommando Anweisung an den Rechner, eine (Folge von) Operation(en) auszuführen. Shell Interaktives Interpreter-Programm für die Eingabe von Kommandos (im Dialog mit dem Benutzer). Prozeß Ausführung eines Programms in einem Rechner. Standard-Eingabe Jeder Prozeß besitzt standardmäßig einen Eingabekanal (System.in) Standard-Ausgabe Jeder Prozeß besitzt standardmäßig einen Ausgabekanal (System.out) Standard-Fehlerausgabe Jeder Prozeß besitzt standardmäßig einen Ausgabekanal für Fehlermeldungen (System.err) 3 1.2 Terminal-Kontrolle Früher wurde ein Rechner von einem separaten Terminal aus benutzt, das über eine serielle Schnittstelle angeschlossen war. Heute ist am PC oder an der Workstation der Bildschirminhalt meistens direkt im Hauptspeicher und wir verwenden ein Programm zur Terminal-Emulation, aber es gibt nach wie vor serielle Anschlüsse für Modems oder an Terminal-Servern. Kommandos ruft man zunächst im Dialog auf. Dazu hat man entweder ein Terminal oder ein Fenster mit einer Terminal-Emulation. Letztlich holt das Betriebssystem Zeichen von der Tastatur ab und schreibt Zeichen auf den Bildschirm. Eine Shell, das heißt, ein Kommando-Interpreter, liest Zeilen und führt die darin beschriebenen Kommandos meistens als eigene Prozesse aus. Normalerweise kann man voraustippen und auch eine Zeile noch korrigieren, bevor sie ein Prozeß sieht. Bestimmte Tasten haben dabei meistens eine besondere Bedeutung, die über das Kommando stty gesteuert werden kann: Taste Delete (^?) Ctrl-W (^W) Ctrl-U (^U) Ctrl-D (^D) Ctrl-C (^C) Ctrl- (^\) Ctrl-Z (^Z) Ctrl-S (^S) Ctrl-Q (^Q) Name erase werase kill eof intr quit susp stop start Bedeutung löscht vorhergehendes Zeichen in der gleichen Zeile löscht vorhergehendes Wort in der gleichen Zeile löscht alle vorhergehenden Zeichen in der gleichen Zeile signalisiert Ende der Eingabe schickt Interrupt-Signal an Prozeß schickt Quit-Signal an Prozeß hält den Prozeß an (weiter mit fg oder bg) hält die Terminal-Ausgabe an setzt die Terminal-Ausgabe fort 4 1.3 Unix-Manual Konventionen Das man-Kommando liefert Informationen über Programme aus dem online-Manual. Es gibt darin (unter anderem) Dokumentation zu allen Shell-Kommandos (ls, cp, rm, mv, mkdir, gzip etc.). Die Beschreibungen verwenden eine vorgegebene, feste Schablone (NAME , DESCRIPTION , EXAMPLES , SEE ALSO , . . . ): SYNOPSIS , $ man column COLUMN(1) System Reference Manual COLUMN(1) NAME column - columnate lists SYNOPSIS column [-tx] [-c columns] [-s sep] [file ...] DESCRIPTION The column utility formats its input into multiple columns. Rows are filled before columns. Input is taken from file operands, or, by default, from the standard input. Empty lines are ignored. The options are as follows: -c Output is formatted for a display columns wide. -s Specify a set of characters to be used to delimit columns for the -t option. -t Determine the number of columns the input contains and create a table. Columns are delimited with whitespace, by default, or with the characters supplied using the -s option. Useful for pretty-printing displays. -x Fill columns before filling rows. Column exits 0 on success, >0 if an error occurred. 5 ENVIRONMENT COLUMNS The environment variable COLUMNS is used to determine the size of the screen if no other information is available. EXAMPLES ls -l | sed 1d | column -t SEE ALSO colrm(1), ls(1), paste(1), sort(1) HISTORY The column command appeared in 4.3BSD-Reno. BSD Experimental June 6, 1993 1 Die Syntax der SYNOPSIS-Zeile ist an BNF angelehnt und auch „so ähnlich“ zu lesen: Worte in [. . . ] sind optional. Alternativen werden mit | getrennt. [-tx] ist hier aber eine Abkürzung für [-t] [-x]. Optionen (Schalter) stehen stets vor Dateinamen, die Reihenfolge der Optionen ist aber beliebig. BNF kann das nicht einfach ausdrücken. Optionen können kombiniert werden: -tx bedeutet -t -x -c80 bedeutet -c 80 -xtc80 bedeutet -t -x -c 80 -- beendet die Optionen und wird ignoriert. - beendet die Optionen und gilt als (Datei)-Argument. Manche Programme (wie z.B. java, cc oder xterm) halten sich leider nicht an diese Konventionen und interpretieren -tx insgesamt als eine Option. . . Neuere Programme erlauben z.T. auch mehrbuchstabige Optionen nach zwei Minuszeichen (z.B. --help), die dann natürlich nicht mit anderen kombiniert werden können. Die Einleitung durch -- statt - vermeidet Konflikte mit den (klassischen) einbuchstabigen Optionen. 6 1.4 Typische Kommandos Shell-interne Kommandos Information Kommunikation Dateien Kataloge Kompaktieren Kopieren Betrachten Anordnen Untersuchen Text ändern Service Prozesse Rechnen Programmieren Subsysteme cd echo pwd . . . cal date df du id logname man tty mail talk who write chgrp chmod chown ln ls mv rm touch find mkdir rmdir gzip unzip zip cp dd tar tee cat file head less more od tail pr sort uniq cmp comm diff egrep fgrep grep wc cut join paste split tr at lpq lpr stty kill nice nohup ps time basename dirname expr env false sleep test true awk make sed sh vi Die Klassifizierung ist nicht strikt. Manche Kommandos passen in mehrere Kategorien. Es gibt natürlich noch viel mehr Kommandos. . . 7 1.5 1.5.1 Shell-Programmierung Was ist ein Kommando? Ein Kommando besteht aus einer Folge von Worten. Das erste Wort in der Eingabe legt fest, welches Kommando ausgeführt wird: $ $ $ $ test date /bin/date ./test eingebaut oder Funktion über PATH gesucht absoluter Pfad relativ, hier im aktuellen Katalog Enthält das erste Wort keinen /, so wird unter den eingebauten Kommandos, dann den Funktionen und dann in jedem Katalog gesucht, der in PATH angegeben ist. Die Variable wird normalerweise bei der Anmeldung ausreichend definiert. $ echo $PATH /home/elmar/bin:/home/elmar/bin/i686:/usr/local/bin:/usr/ local/gnu/bin:/usr/ucb:/usr/bin:/bin:/usr/X11R6/bin:/usr/ lib/java/bin PATH kann den aktuellen Katalog als . oder als leeren Eintrag enthalten. Beim SuperUser enthält PATH den aktuellen Katalog a priori aus Sicherheitsgründen nicht. type zeigt, wo ein Kommando im Dateisystem zuerst gefunden wird. Das Kommando ist in der Shell eingebaut: $ type date echo date is /usr/bin/date echo is a shell builtin 1.5.2 Was ist ein Wort? Zwischenraum besteht aus Leerzeichen und Tabulatorzeichen. Zwischenraum trennt Worte. Die Folge newline wird insgesamt entfernt – sie ist kein Zwischenraum. Worte sind Zeichenfolgen, die durch Zwischenraum getrennt sind. Zitierter Zwischenraum (mit \ oder einfachen oder doppelten Anführungszeichen) zählt als Teil eines Worts. 8 zitiert das nächste Zeichen; newline wird ignoriert. ’. . . ’ zitiert alle Zeichen außer einem einfachen Anführungszeichen. ". . . " erlaubt Textersatz für Variablen, Kommandos und Arithmetik. \ zitiert ‘, ", $ und newline. Kommentare beginnen mit # am Wortanfang und reichen bis zum Zeilenende. 1.5.3 Textersatz für Variablen Zuweisung an eine Shell-Variable ist ein Wort, das aus einem Variablennamen, einem Gleichheitszeichen und dem neuen Wert besteht. Der neue Wert ist beliebiger Text, also auch leer. Die Werte von Shell-Variablen werden durch $name oder auch ${name} abgerufen. Dabei werden die Werte an Zwischenraum (siehe IFS) in Worte zerlegt, wenn der Ausdruck nicht in doppelten Anführungszeichen ". . . " steht. $ a="hallo welt" $ echo $a hallo welt $ echo ${a}bc hallo weltbc $ echo $abc $ ls $a ls: hallo: No such file or directory ls: welt: No such file or directory $ ls "$a" ls: hallo welt: No such file or directory $ a=’$b’ $ echo $a $b Mit Doppelanführungszeichen kann man Variablen einigermaßen als Wertelisten verwenden, dabei können die Werte allerdings keinen Zwischenraum enthalten: $ list= $ for name in A B C; do 9 > list="$list $name.class" > done $ echo $list A.class B.class C.class Mit der export-Anweisung werden einzelne Shell-Variablen an die aus der Shell aufgerufenen Kommandos weitergereicht: $ a=hallo $ printenv a $ export a $ printenv a hallo Zuweisungen können auch unmittelbar vor einem Kommando stehen und gelten dann nur für die Ausführung dieses Kommandos (export erfolgt dann automatisch): $ x=x $ x=y printenv x y $ echo $x x Die Shell setzt einige spezielle Variablen: $1 . . . $9 $# $* und $@ "$*" "$@" $? Argumente des Interpreters (oder Funktion) Anzahl der Argumente alle Argumente alle Argumente als ein Wort alle Argumente als einzelne Wörter Exit-Code des letzten Kommandos Das System verwendet bestimmte Variablen – eine Auswahl: HOME IFS LANG LOGNAME PATH PS1 PS2 SHELL TERM Heimatkatalog (z.B. für cd) trennende Zeichen für Worte (space, tab, newline) Spracheinstellung (en_US) Login des Benutzers Suchpfad für Kommandos Prompt (’$ ’) Prompt bei Fortsetzung (’> ’) Name der Shell Terminaltyp (vt100) 10 1.5.4 Textersatz für Kommandos Ein Kommando in $(. . . ) (oder ‘. . . ‘) wird durch seine Standard-Ausgabe ersetzt. Zeilentrenner am Schluß werden dabei entfernt: $ echo "user $(whoami) on ‘hostname‘" user elmar on suleika Kommandos werden auch innerhalb von ". . . " ersetzt, wobei dann nur ein Argument als Resultat entsteht. Sonst wird die Ausgabe wie bei Variablen an den Zeichen in Worte zerlegt, die in IFS stehen: $ IFS=: $ echo $(head -1 /etc/passwd) root x 0 0 root /root /bin/bash Allgemein schützen ’. . . ’ und ". . . " vor Nachzerlegung an IFS. Ungeschützte Argumente, Variablenwerte und Kommando-Ausgabe werden in Worte nachzerlegt, Muster für Dateinamen aber nicht: $ touch a b "a b" $ ls -l -rw-r--r-1 elmar -rw-r--r-1 elmar -rw-r--r-1 elmar $ for i in [ab]*; do > echo ":$i:" > done :a: :a b: :b: 1.5.5 inform inform inform 0 Mar 27 17:24 a 0 Mar 27 17:24 a b 0 Mar 27 17:24 b Arithmetik mit Variablen Ein ganzzahliger arithmetischer Ausdruck in $((. . . )) wird durch den numerischen Wert des Ausdrucks ersetzt. Leider wird das nicht von allen Shells unterstützt. . . $ x=1 $ x=$((x + 1)) $ echo $x 2 Es gibt praktisch alle aus C oder Java bekannten Operatoren. 11 1.5.6 Textersatz für Argumente Argumente des Interpreters sind in $1 bis $9 gespeichert (entspricht dem args[] in der main()-Methode in Java). Der Name des Shell-Skripts selbst ist $0. Eine Funktion verwendet die gleichen Argumente wie die Shell selbst, das heißt, ein Funktionsaufruf zerstört die Argumentliste der Shell: $ echo $* $ f() { > echo foo > } $ f 3 foo $ echo $* 3 Alle Argumente (auch mehr als 9) sind durch $* oder $@ erreichbar. "$*" liefert sie als ein Wort, verkettet mit Leerzeichen. "$@" liefert sie als ein Argument pro Wort. 1.5.7 Dateinamen Nach Ersatz von Variablen und Kommandos werden aus den nicht zitierten Wörtern – also auch aus dem Ersatztext von Variablen – Dateinamen generiert. Aus einem Wort entsteht jeweils eine alphabetisch sortierte Namensliste – die Listen werden nicht zusammengefügt. Das Resultat der Generierung wird weder nachersetzt noch an IFS zerlegt. $ cp *.java /tmp $ ls */*.class tmp/NeighborQueen.class tmp/Queen.class tmp/QueensIM.class $ echo hi > ab $ a=* $ echo "$a" * $ echo $a ab tmp/QueensOO.class tmp/SimpleQueen.class tmp Allgemein folgt, daß man Variablen und Kommandos beim Einsetzen wohl grundsätzlich mit ". . . " schützen sollte, falls man keine Zerlegung braucht: 12 $ read x * $ echo $x ab tmp 1.5.8 Muster für Dateinamen Es gibt eine Reihe von Sonderzeichen, die in Mustern für Dateinamen oder der caseAnweisung (siehe 1.5.11 auf Seite 15) eine besondere Bedeutung haben: normalerweise stellt ein Zeichen sich selbst dar \ ’ . . . ’ " . . . " Sonderbedeutung unterdrücken beliebiges, einzelnes Zeichen beliebig viele beliebige Zeichen einzelnes Zeichen aus Klasse, auch Bereiche wie a-z einzelnes Zeichen nicht aus Klasse und dann ? * [. . . ] [!. . . ] | oder (nicht für Dateinamen, nur bei case-Anweisung!) Bei Dateinamen werden . am Anfang und / überall nur explizit erkannt. Die Muster sind implizit links uns rechts verankert. Beispiele: *.java [A-Z]* [!A-Z]* 1.5.9 Java-Quellen Dateinamen mit großem Anfangsbuchstaben (fast) alle anderen Dateinamen (bis auf .*) Ein-/Ausgabe-Umlenkung Eine Ein-/Ausgabe-Umlenkung beginnt mit > (Ausgabe in Datei), >> (Ausgabe an Datei anfügen), < (Eingabe aus Datei). Davor kann noch eine Ziffer stehen, die den File-Deskriptor bezeichnet, auf den sich die Umlenkung bezieht: 0 (default) für die Standard-Eingabe, 1 (default) für Standard-Ausgabe, 2 für Standard-Fehlerausgabe. Der Rest des Worts oder das nächste Wort ist ein Dateiname. $ wc *.java >output $ javac X.java 2>errors $ wc <errors 2 6 35 13 1.5.10 Zusammengesetzte Kommandos einfaches Kommando besteht aus einer expandierten Folge von Worten, abgeschlossen durch Zeilentrenner oder andere Zeichen (newline, ; oder &). Kommando ist ein einfaches Kommando oder eine Kontrollstruktur (siehe 1.5.11). Pipeline | ist eine Folge von Kommandos, unterteilt mit |. Die Standard-Ausgabe des Kommandos links in der Pipeline wird jeweils zur Standard-Eingabe des Kommandos rechts in der Pipeline. $ date | wc 1 6 30 $ date | tr a-z A-Z WED APR 3 09:41:55 MEST 2002 $ du /tmp | sort -n | tail 4 /tmp/.font-unix 4 /tmp/elmar 4 /tmp/nscomm40-olaf/29939 4 /tmp/nscomm40-olaf/9539 4 /tmp/nscomm40-olaf/9772 8 /tmp/ksocket-elmar 8 /tmp/mcop-elmar 16 /tmp/nscomm40-olaf 444 /tmp/kde-elmar 588 /tmp logische Liste && || ist eine Folge von Pipelines, unterteilt mit && oder ||. Die Folge wird von links her abgearbeitet: a && b a || b Kommando b nur, wenn Kommando a den Exit-Code 0 liefert. Kommando b nur, wenn Kommando a nicht Exit-Code 0 liefert. Liste newline ; & ist eine Folge von logischen Listen, abgetrennt mit beliebig vielen Zeilentrennern oder Semikolons, damit Kommandos nacheinander abgearbeitet werden. Eine mit & beendete logische Liste wird asynchron abgearbeitet, das heißt, die Shell wartet nicht auf das Prozeßende. $ test -f file && rm file & 14 insgesamt im Hintergrund 1.5.11 Kontrollstrukturen Ein Kommando ist entweder eine Kommando-Liste, also auch eine Pipeline oder ein einfaches Kommando, oder es ist eine Kontrollstruktur: for variable in wort . . . do liste done Beispiel: $ for x in $(seq 0 9); do > echo $x > done Das seq-Kommando kann Zahlensequenzen für for generieren. while [ bedingung ] do liste done Eine Bedingung ist ein Boolescher Ausdruck, der (unter anderem) die folgenden Operatoren enthalten kann: ( ! ) -a -o [-n] -z = != -eq -ge -gt -le -lt -ne -d -f -r is true is false both and are true either or is true the length of is non-zero the length of is zero the strings are equal the strings are not equal is equal to is greater than or equal to is greater than is less than or equal to is less than is not equal to exists and is a directory exists and is a regular file exists and is readable Vorsicht: Runde Klammern müssen durch Anführungszeichen geschützt werden! 15 Beispiel: $ x=0 $ while [ $x != 10 ]; do > echo $x > x=$((x + 1)) > done until [ bedingung ] do liste done Wie while-Schleife, aber negierte Bedingung. if [ bedingung ]; then liste elif [ bedingung ]; then liste else liste fi Der elif -Teil kann häufig wiederholt werden (und ist optional), der else-Teil ist ebenfalls optional. Beispiel: $ if [ ! -d $dir ]; then > mkdir $dir > fi case wort in muster | . . . ) wie Dateimuster, erkennen auch / liste ;; muster | . . . ) viele Fälle liste esac Fallverteiler mit Musterbewertung. Nur der erste passende Fall wird ausgeführt: $ > > > > > > case "$name" in [A-Z]*) echo upper case ;; [a-z]*) echo lower case ;; *) echo something else esac 16 { liste ; } Zusammenfassung von Kommandos name() { liste } definiert Funktion (Argumente sind $1 bis $9 bzw. $*, siehe 1.5.6) Man kann rekursiv aufrufen, es gibt aber keine lokalen Variablen (ohne Sub-Shells zu verwenden, die hier nicht erklärt werden). Ein-/Ausgabe-Umlenkung ist für die ganze Kontrollstruktur möglich, denn sie gilt wieder als Kommando. Das führt dann aber zu Sub-Shells. Der Exit-Code stammt vom letzten Kommando – if, while und until liefern Null, wenn kein Kommando ausgeführt worden ist. Vorsicht: Die ganzen steuernden Worte sind nicht reserviert; insbesondere sind nur die runden Klammern reserviert, die geschweiften nicht! 1.5.12 Eingebaute Kommandos Eine Reihe von Kommandos sind in der Shell eingebaut – Kommandos wie cd oder exit wirken auf die Shell selbst, bei anderen wie echo oder test geschieht das aus Effizienzgründen: : Kommentar; wird aber ausgeführt und liefert 0. . datei datei wird auf PATH gesucht und in der gleichen Shell ausgeführt – damit kann man per Skript Variablen in der eigenen Shell setzen. break [anzahl] for-, while- und until-Schleifen verlassen. cd [pfad] Arbeitskatalog auf HOME oder pfad setzen. continue [anzahl] for-, while- und until-Schleifen fortsetzen. echo [arg . . . ] Argumente ausgeben (Vorsicht bei \). eval arg . . . Argumente einlesen, parsieren und ausführen – damit findet vor allem nochmals 17 Textersatz und Zerlegung statt: $ a=hallo $ b=’$a’ $ echo $b $a $ b=$b $ echo $b $a $ eval b=$b $ echo $b hallo exit [code] Shell verlassen. Exit-Code ist code oder stammt vom letzten Kommando. export [name . . . ] Variablen für Export markieren oder exportierte zeigen. pwd Arbeitskatalog zeigen. read name . . . Eine Zeile einlesen, an IFS zerlegen und die Wörter an die Variablen zuweisen. Restliche Wörter gehen (unzerlegt) an die letzte Variable. return [code] Funktion verlassen. Exit-Code ist code oder stammt vom letzten Kommando. shift [anzahl] Argumente „nach links schieben“. Das erste oder die anzahl ersten Argumente gehen verloren, $*, $@ und $# werden korrigiert. test [arg . . . ] Bedingungen prüfen. type name . . . Ausgeben, was name als Kommando bedeutet. unset name . . . Variable oder Funktion löschen. 18 1.6 Make Das make-Kommando ([Feldman]) erlaubt die automatisierte Ausführung von Kommandosequenzen in Abhängigkeit von Änderungen an Dateien. Typische Anwendungen für make sind Fälle in denen Ausgabedateien über Kommandos (wie cc, javac oder latex) automatisiert aus Eingabedateien erzeugt werden. Ein einfaches Beispiel: Es gebe 3 Listen mit Namen, die sich gelegentlich (einzeln) ändern (z.B. weil sie von unterschiedlichen Personen aktualisiert werden). Aufgabe ist nun, mit minimalem Aufwand eine moeglichst permanent aktuelle Gesamtliste in originaler und zusätzlich noch in alphabetischer Reihenfolge zu unterhalten. Lösung 1: Führe nach jeder Änderung in einer der drei Dateien „von Hand“ die folgenden Befehle aus: $ cat datei1 datei2 datei3 >liste $ sort liste >liste.sortiert Aber: – Befehle „von Hand“ tippen ist lästig und fehleranfällig. – Möglicherweise kann man Arbeit sparen, wenn man „weiß“, welche der Dateien sich geändert haben. – Vielleicht möchte man nicht immer alle Aktionen ausführen. Lösung 2: Schreibe die Anweisungen zum Aktualisieren der Ausgabedateien einmal auf, den Rest kann ein Werkzeug erledigen. Und genau dieses Werkzeug ist make: liste: datei1 datei2 datei3 cat datei1 datei2 datei3 >liste liste.sortiert: liste sort liste >liste.sortiert Abbildung 1.1: Inhalt der Datei Makefile $ make liste.sortiert cat datei1 datei2 datei3 >liste sort liste >liste.sortiert 19 Ein Makefile ist eine einfache Textdatei mit Anweisungen für das make-Kommando. Der Name ist entweder makefile, Makefile oder kann über die -f Option von make angegeben werden. Ein Makefile kann drei verschiedene Arten von „Anweisungen“ für make enthalten (alles zeilenorientiert, Eingabezeilen können bei Bedarf mit \ am Zeilenende fortgesetzt werden): Variablendefinition Auch make kennt – wie die Shell – Variablen. Allerdings sind diese getrennt von den Shellvariablen: Eine in der Shell definierte Variable ist nicht automatisch auch in make bekannt (und umgekehrt). Variablen werden ähnlich wie in der Shell definiert: variable = wort . . . Die Regeln sind hier aber weniger strikt als in der Shell: Zwischenraum vor und nach dem Gleichheitszeichen ist erlaubt (und wird ignoriert), und die Definition geht immer bis zum Ende der aktuellen (logischen) Zeile, d.h. der Wert kann Zwischenraum enthalten. Anführungszeichen habe keine besondere Bedeutung. Die Werte von Shell-Variablen werden durch $(name) oder auch ${name} abgerufen. Dabei werden auch hier die Werte an Zwischenraum in Worte zerlegt, d.h. man kann Listen bilden. (Explizite) Regel Ein Eintrag der Form: ziel : quelle . . . shell-kommandos definiert ein „Ziel“, daß make erzeugen kann. Rechts von dem Doppelpunkt stehen alle Dateien, aus denen das Ziel erzeugt wird (Liste kann leer sein). Alle folgenden Zeilen, die mit einem TAB eingerückt sind, enthalten die ShellKommandos zum Erstellen der Ziel-Datei. Implizite Regel Quasi eine „Wildcard“ für eine normale Regel. Sieht genau wie eine explizite Regel aus, verwendet aber den Platzhalter % als Symbol für eine beliebige Zeichenfolge im Namen des Ziels und der Quelle. Typisches Beispiel für Java: %.class : %.java javac -g $< 20 Es gibt in impliziten Regeln vordefinierte Variablen wie $@ (aktuelles Ziel), $< (erste Datei aus Quell-Datei Liste), $ˆ (alle Quell-Dateien) etc. Ein komplexeres Beispiel: # java compiler, flags CLASSPATH = . JAVAC = javac -classpath $(CLASSPATH) JAVADOC = javadoc -classpath $(CLASSPATH) JFLAGS = -g # list of classes to build and remove SOURCES = IO.java CLASSES = $(SOURCES:.java=.class) CLEAN = $(CLASSES) *.class # wildcard rule %.class : %.java $(JAVAC) $(JFLAGS) $< # default target all : $(CLASSES) doc : $(SOURCES) rm -rf $@; mkdir $@ $(JAVADOC) -d $@ $(SOURCES) # clean up clean : rm -f $(CLEAN) rm -rf doc 21 Literaturverzeichnis [ATS 94] Axel T. Schreiner: Shell-Programmierung, Osnabrücker Schriften zur Mathematik, 1994 [Kernighan 84] Brian Kernighan, Rob Pike: The Unix Programming Environment, Prentice-Hall, 1984 [Robbins] Arnold Robbins: Opening the Software Toolbox, Linux Journal, Volume 1, Number 2 [Feldman] Stu Feldman: Make - A Program for Maintaining Computer Programs 22