\chapter{Schmutzige Tricks :-)} Eigentlich sind diese Tricks gar nicht so schmutzig. Hier ist lediglich eine Sammlung von Beispielen, die genial einfach oder genial gut programmiert sind. Das bedeutet nicht, daß jeder Shell-Programmierer diese Techniken benutzen sollte. Ganz im Gegenteil. Einige Mechanismen bergen Gefahren, die nicht auf den ersten Blick erkennbar sind. Mit anderen Worten: \textbf{Wenn Du diese Techniken nicht verstehst, dann benutze sie nicht!} Die Intention hinter diesem Abschnitt ist es, dem gelangweilten Skripter etwas interessantes zum Lesen zu geben. Das inspiriert dann vielleicht dazu, doch einmal in den fortgeschrittenen Bereich vorzustoßen, neue Techniken kennenzulernen. Außerdem kann das Wissen über gewisse Techniken eine große Hilfe beim Lesen fremder Skripte darstellen, die eventuell von diesen Techniken Gebrauch machen. Diese Techniken sind nicht `auf meinem Mist gewachsen', sie stammen vielmehr aus Skripten, die mir im Laufe der Zeit in die Finger gekommen sind. Ich danke an dieser Stelle den klugen Köpfen, die sich solche Sachen einfallen lassen haben. \section{Die Tar-Brücke} TODO!!! \section{Binaries inside} TODO!!! \subsection{Binäre Here-Dokumente} TODO!!! \subsection{Schwanz ab!} TODO!!! \section{Dateien, die es nicht gibt} TODO!!! \subsection{Speichern in nicht existente Dateien} TODO!!! \subsection{Subshell-Schleifen vermeiden} Wir wollen ein Skript schreiben, das die \texttt{/etc/passwd} liest und dabei zählt, wie viele Benutzer eine UID kleiner als 100 haben. Folgendes Skript funktioniert nicht: \footnotesize \begin{listing}[2]{1} #!/bin/sh count=0 cat /etc/passwd | while read i; do uid=`echo $i | cut -f 3 -d:` if [ $uid -lt 100 ]; then count=`expr $count + 1` echo $count fi done echo Es sind $count Benutzer mit einer ID kleiner 100 eingetragen \end{listing} \normalsize Was ist passiert? Dieses Skript besteht im Wesentlichen aus einer Pipe. Wir haben ein \texttt{cat}-Kommando, das den Inhalt der \texttt{/etc/passwd} durch eben diese Pipe an eine Schleife übergibt. Das \texttt{read}-Kommando in der Schleife liest die einzelnen Zeilen aus, dann folgt ein Bißchen Auswertung. Es ist zu beobachten, daß bei der Ausgabe in Zeile 7 die Variable \texttt{\$count} korrekte Werte enthält. Um so unverständlicher ist es, daß sie nach der Vollendung der Schleife wieder den Wert 0 enthält. Das liegt daran, daß diese Schleife als Teil einer Pipe in einer Subshell ausgeführt wird. Die Variable \texttt{\$count} steht damit in der Schleife praktisch nur lokal zur Verfügung, sie wird nicht an das umgebende Skript 'hochgereicht'. Neben der Methode in \ref{daten_hochreichen} bietet sich hier eine viel einfachere Lösung an: \footnotesize \begin{listing}[2]{1} #!/bin/sh count=0 while read i; do uid=`echo $i | cut -f 3 -d:` if [ $uid -lt 100 ]; then count=`expr $count + 1` echo $count fi done < /etc/passwd echo Es sind $count Benutzer mit einer ID kleiner 100 eingetragen \end{listing} \normalsize Hier befindet sich die Schleife nicht in einer Pipe, daher wird sie auch nicht in einer Subshell ausgeführt. Man kann auf das \texttt{cat}-Kommando verzichten und den Inhalt der Datei durch die Umlenkung in Zeile 9 direkt auf die Standardeingabe der Schleife (und somit auf das \texttt{read}-Kommando) legen. \subsection{Daten aus einer Subshell hochreichen}\label{daten_hochreichen} TODO!!! \subsection{Dateien gleichzeitig lesen und schreiben} Es kommt vor, daß man eine Datei bearbeiten möchte, die hinterher aber wieder unter dem gleichen Namen zur Verfügung stehen soll. Beispielsweise sollen alle Zeilen aus einer Datei entfernt werden, die nicht das Wort \textit{wichtig} enthalten. Der erste Versuch an der Stelle wird etwas in der Form \texttt{grep }\textit{wichtig datei.txt}\texttt{ > }\textit{datei.txt} sein. Das kann funktionieren, es kann aber auch in die sprichwörtliche Hose gehen. Das Problem an der Stelle ist, daß die Datei an der Stelle gleichzeitig zum Lesen und zum Schreiben geöffnet wird. Das Ergebnis ist undefiniert. Eine Elegante Lösung besteht darin, einen Filedeskriptor auf die Quelldatei zu legen und diese dann zu löschen. Die Datei wird erst dann wirklich aus dem Dateisystem entfernt, wenn kein Deskriptor mehr auf sie zeigt. Dann kann aus dem gerade angelegten Deskriptor gelesen werden, während eine neue Datei unter dem alten Namen angelegt wird: \footnotesize \begin{listing}[2]{1} #!/bin/sh FILE=datei.txt exec 3< "$FILE" rm "$FILE" grep "wichtig" <&3 > "$FILE" \end{listing} \normalsize Allerdings sollte man bei dieser Methode beachten, daß man im Falle eines Fehlers die Quelldaten verliert, da die Datei ja bereits gelöscht wurde.