diff --git a/quellen.tex b/quellen.tex index 4b220f6..ff05cf6 100644 --- a/quellen.tex +++ b/quellen.tex @@ -2,17 +2,17 @@ \chapter{Quellen}\label{quellen} \begin{itemize} -\item Bash Reference Manual (\texttt{http://www.gnu.org/manual/bash-2.02/\\bashref.html}) +\item Bash Reference Manual (\href{http://www.gnu.org/manual/bash-2.02/bashref.html}{http://www.gnu.org/manual/bash-2.02/bashref.html}) \item Die Man-Page der Bash -\item Die deutschsprachige Shell-Newsgroup (\texttt{de.comp.os.unix.shell}) -\item Unix In A Nutshell (\texttt{http://www.oreilly.com/catalog/unixnut3/}) -\item Unix Power Tools (\texttt{http://www.oreilly.com/catalog/upt2/}) -\item Von DOS nach Linux HOWTO (\texttt{http://www.linuxhaven.de/dlhp/HOWTO/DE-DOS-nach-Linux-HOWTO.html}) +\item Die deutschsprachige Shell-Newsgroup (\href{news://de.comp.os.unix.shell}{news://de.comp.os.unix.shell}) +\item Unix In A Nutshell (\href{http://www.oreilly.com/catalog/unixnut3/}{http://www.oreilly.com/catalog/unixnut3/}) +\item Unix Power Tools (\href{http://www.oreilly.com/catalog/upt2/}{http://www.oreilly.com/catalog/upt2/}) +\item Von DOS nach Linux HOWTO (\href{http://www.linuxhaven.de/dlhp/HOWTO/DE-DOS-nach-Linux-HOWTO.html}{http://www.linuxhaven.de/dlhp/HOWTO/DE-DOS-nach-Linux-HOWTO.html}) TODO!!! Andere URL angeben (http://www.tldp.org...) -\item Bash Guide for Beginners (\texttt{http://tldp.org/LDP/Bash-Beginners-Guide/}) -\item Advanced Bash-Scripting Guide (\texttt{http://tldp.org/LDP/abs/}) +\item Bash Guide for Beginners (\href{http://tldp.org/LDP/Bash-Beginners-Guide/}{http://tldp.org/LDP/Bash-Beginners-Guide/}) +\item Advanced Bash-Scripting Guide (\href{http://tldp.org/LDP/abs/}{http://tldp.org/LDP/abs/}) \item ... und eine Menge Skripte, die ich im Laufe der Zeit gelesen habe (das kann ich nur jedem empfehlen - es ist spannender als es sich anhört...). \end{itemize} diff --git a/schmutzige_tricks.tex b/schmutzige_tricks.tex index ee545fa..8218468 100644 --- a/schmutzige_tricks.tex +++ b/schmutzige_tricks.tex @@ -36,7 +36,7 @@ einzupacken, dies durch eine Pipe auf die andere Seite der Verbindung zu bringen und dort wieder zu entpacken. Wenn dem Kommando \texttt{tar} an Stelle eines Dateinamens ein Minus-Zeichen -als Archiv gegeben wird, benutzt es~---~je nach der gewählten Aktion~---~die +als Archiv gegeben wird, benutzt es~--~je nach der gewählten Aktion~--~die Standard-Ein- bzw. -Ausgabe. Diese kann an ein weiteres \texttt{tar} übergeben werden um wieder entpackt zu werden. @@ -88,15 +88,112 @@ der Name des aktuellen Verzeichnisses auf dem lokalen System. \section{Binaries inside} -TODO!!! binaries inside +Software wird meistens in Form von Paketen verteilt. Entweder handelt es sich +dabei um auf das Betriebssystem abgestimmte Installationspakete (rpm, deb, pkg +usw.), gepackte Archive (zip, tgz) oder Installationsprogramme. Unter +Unix-Systemen bietet sich für letztere die Shell als Trägersystem an. +Shell-Skripte sind mit wenigen Einschränkungen plattformunabhängig, sie können +also ohne vorherige Installations- oder Compilier-Arbeiten gestartet werden und +die Umgebung für das zu installierende Programm testen und / oder vorbereiten. + +Abgesehen davon können Skripte mit den hier vorgestellten Techniken auch andere +Daten, z. B. Bilder oder Töne, enthalten. + +Doch wie werden die~--~üblicherweise binären~--~Pakete auf das Zielsystem +gebracht? + +Im Prinzip gibt es dafür zwei unterschiedliche Verfahren: \subsection{Binäre Here-Dokumente} +\index{Here-Dokument} -TODO!!! binäre Here-Dokumente +Eine Möglichkeit ist es, die binäre Datei in Form eines Here-Dokuments +mitzuliefern. Da es aber in der Natur einer binären Datei liegt nicht-druckbare +Zeichen zu enthalten, kann die Datei mit Hilfe des Tools \texttt{uuencode} +vorbereitet werden. Das Tool codiert Eingabedateien so, daß sie nur noch +einfache Textzeichen enthalten. + +Sehen wir uns das folgende einfache Beispiel an. Es ist etwas wild konstruiert +und nicht sehr sinnvoll, aber es zeigt das Prinzip. + +\begin{lstlisting} +#!/bin/sh + +echo "Das Bild wird ausgepackt..." + +uudecode << 'EOF' +begin 644 icon.png +MB5!.1PT*&@H````-24A$4@```!8````6"`8```#$M&P[````"7!(67,```L3 +M```+$P$`FIP8````!&=!34$``+&.?/M1DP```"!C2%)-``!Z)0``@(,``/G_ +\end{lstlisting} + +Nach einem Hinweis wird also das Here-Dokument als Eingabe für das Tool +\texttt{uudecode} benutzt. Erstellt wurde das Dokument mit einer Zeile in der +Form \texttt{uuencode icon.png icon.png}. + +Wie man sieht ist der Name der Datei in dem Here-Dokument enthalten. Die Datei +wird entpackt und unter diesem gespeichert. In der `realen Welt' muß an der +Stelle auf jeden Fall sichergestellt werden, daß keine existierenden Dateien +versehentlich überschrieben werden. + +Um diesen Abschnitt nicht allzu lang werden zu lassen überspringen wir einen +Teil der Datei. + +\begin{lstlisting}[firstnumber=38] +M#-""F4%,@%4.GUZ``"(*`6VW6!S#\>C_?/;__Q@``B +K!>E;2S-,]5!A7`,,U'0@GQ6?8H```P`#@&?)O'P'L0````!)14Y$KD)@@@`` +` +end +EOF + +if [ $? -ne 0 ]; then + echo "Fehler beim Auspacken der Datei" + exit 1 +fi + +display icon.png +\end{lstlisting} + +Nach dem Entpacken wird noch der Exit-Code von \texttt{uudecode} überprüft und +im Fehlerfall eine Ausgabe gemacht. Im Erfolgsfall wird das Bild mittels +\texttt{display} angezeigt. \subsection{Schwanz ab!} -TODO!!! Schwanz ab +Diese Variante basiert darauf, daß die binäre Datei ohne weitere Codierung an +das Shell-Skript angehängt wurde. Nachteil dieses Verfahrens ist, daß das +`abschneidende Kommando' nach jeder Änderung der Länge des Skriptes angepaßt +werden muß. + +Dabei gibt es zwei Methoden, die angehängte Datei wieder abzuschneiden. Die +einfachere Methode funktioniert mit \texttt{tail}: + +\texttt{tail -n +227 \$0 > icon.png} + +Dieses Beispiel geht davon aus, daß das Skript selbst 227 Zeilen umfaßt. Die +binäre Datei wurde mit einem Kommando wie \texttt{cat icon.png >> skript.sh} an +das Skript angehängt. + +Für die etwas kompliziertere Variante muß die Länge des eigentlichen +Skript-Teiles genau angepaßt werden. Wenn das Skript beispielsweise etwa 5,5kB +lang ist, müssen genau passend viele Leerzeilen oder Kommentarzeichen angehängt +werden, damit sich eine Länge von 6kB ergibt. Dann kann das Anhängsel mit dem +Kommando \texttt{dd} in der folgenden Form abgeschnitten werden: + +\texttt{dd bs=1024 if=\$0 of=icon.png skip=6} + +Das Kommando kopiert Daten aus einer Eingabe- in eine Ausgabedatei. Im +einzelnen wird hier eine Blockgröße (blocksize, bs) von 1024 Bytes festgelegt. +Dann werden Eingabe- und Ausgabedatei benannt, dabei wird als Eingabedatei +\texttt{\$0} und somit der Name des laufenden Skriptes benutzt. Schließlich +wird festgelegt, daß bei der Aktion die ersten sechs Block~--~also die ersten +sechs Kilobytes~--~übersprungen werden sollen. + +Um es nochmal zu betonen: Diese beiden Methoden sind mit Vorsicht zu genießen. +Bei der ersten führt jede zusätzliche oder gelöschte Zeile zu einer kaputten +Ausgabedatei, bei der zweiten reichen schon einzelne Zeilen. In jedem Fall +sollte nach dem Auspacken noch einmal mittels \texttt{sum} oder \texttt{md5sum} +eine Checksumme gezogen und verglichen werden. \section{Dateien, die es nicht gibt} @@ -165,7 +262,97 @@ Standardeingabe der Schleife (und somit auf das \texttt{read}-Kommando) legen. \subsection{Daten aus einer Subshell hochreichen}\label{daten_hochreichen} -TODO!!! Daten aus einer Subshell hochreichen +Ein immer wieder auftretendes und oft sehr verwirrendes Problem ist, daß +Variablen die in einer Subshell definiert wurden außerhalb dieser nicht +sichtbar sind (siehe Abschnitt \ref{subshellschleifen}). Dies ist um so +ärgerlicher, als daß Subshells auch bei vergleichsweise einfachen Pipelines +geöffnet werden. + +Ein Beispiel für ein mißlingendes Skriptfragment wäre das folgende: + +\begin{lstlisting} +nCounter=0 +cat datei.txt | while read VAR; do + nCounter=`expr $nCounter + 1` +done +echo "nCounter=$nCounter" +\end{lstlisting} + +Die Variable nCounter wird mit 0 initialisiert. Dann wird eine Datei per Pipe +in eine \texttt{while}-Schleife geleitet. Innerhalb der Schleife wird für jede +eingehende Zeile die Variable hochgezählt. Am Ende der Schleife enthält die +Variable tatsächlich den korrekten Wert, aber da die Pipe eine Subshell +geöffnet hat ist der Wert nach Beendigung der Schleife nicht mehr sichtbar. Das +\texttt{echo}-Kommando gibt die Zahl 0 aus. + +Es gibt mehrere Ansätze, diesem Problem zu begegnen. Am einfachsten wäre es in +diesem Fall, dem Rat aus Abschnitt \ref{subshellschleifen} zu folgen und die +Subshell geschickt zu vermeiden. Doch das ist leider nicht immer möglich. Wie +geht man in solchen Fällen vor? + +Bei einfachen Zahlenwerten könnte beispielsweise ein Rückgabewert helfen. +Komplexere Informationen können in eine temporäre Datei geschrieben werden, die +danach geparst werden müßte. Wenn die Informationen in Zeilen der Form +`VARIABLE=\dq{}Wert\dq{}' gespeichert werden, kann die Datei einfach mittels +\texttt{source} (Abschnitt \ref{source}) oder einem Konstrukt der Art +\texttt{eval `cat tempfile`} gelesen werden. + +Und genau mit dieser Überlegung kommen wir zu einem eleganten~--~wenn auch +nicht ganz einfachen~--~Trick. + +Anstatt die Daten in eine temporäre Datei zu schreiben, wo sie womöglich durch +andere Prozesse verändert oder ausgelesen werden könnten, kann man sie auch in +`nicht existente' Dateien schreiben. Das folgende Beispiel demonstriert das +Verfahren: + +\begin{lstlisting} +#!/bin/sh -x +TMPNAME="/tmp/`date '+%Y%m%d%H%M%S'`$$.txt" +exec 3> "$TMPNAME" +exec 4< "$TMPNAME" +rm -f "$TMPNAME" +\end{lstlisting} + +Bis hierher wurde zunächst eine temporäre Datei angelegt. Die Filehandles 3 und +4 wurden zum Schreiben bzw. Lesen mit dieser Datei verbunden. Daraufhin wurde +die Datei entfernt. Die Filehandles verweisen weiterhin auf die Datei, obwohl +sie im Dateisystem nicht mehr sichtbar ist. + +Kommen wir zum nützlichen Teil des Skriptes: + +\begin{lstlisting}[firstnumber=6] +nCounter=0 +cat datei.txt | ( while read VAR; do + while read VAR; do + nCounter=`expr $nCounter + 1` + done + echo "nCounter=$nCounter" +) >&3 +\end{lstlisting} + +Hier wurde wieder die Variable nCounter initialisiert und in der Subshell die +Zeilen gezählt wie im ersten Beispiel. Allerdings wurde explizit eine Subshell +um die Schleife gestartet. Dadurch steht die in der Schleife hochgezählte +Variable auch nach Beendigung der Schleife zur Verfügung, allerdings immernoch +nur in der Subshell. Um das zu ändern, wird in Zeile 11 der Wert ausgegeben. +Die Ausgaben der Subshell werden in den oben erstellen Deskriptor umgeleitet. + +\begin{lstlisting}[firstnumber=13] +echo "(vor eval) nCounter=$nCounter" +eval `cat <&4` +echo "(nach eval) nCounter=$nCounter" +\end{lstlisting} + +Das \texttt{echo}-Kommando in Zeile 13 beweist, daß der Wert von nCounter +tatsächlich außerhalb der Subshell nicht zur Verfügung steht. Zunächst. + +In Zeile 14 wird dann die ebenfalls oben schon angesprochene +\texttt{eval}-Zeile benutzt, um die Informationen aus dem Filedeskriptor zu +lesen, die die Schleife dort hinterlassen hat. + +Abschließend zeigt die Zeile 15, daß der Transport tatsächlich funktioniert +hat, die Variable nCounter ist mit dem Wert aus der Subshell belegt. + \subsection{Dateien gleichzeitig lesen und schreiben} @@ -199,7 +386,56 @@ grep "wichtig" <&3 > "$FILE" Allerdings sollte man bei dieser Methode beachten, daß man im Falle eines Fehlers die Quelldaten verliert, da die Datei ja bereits gelöscht wurde. -\section{Auf der Lauer: Wachhunde} +\section{Auf der Lauer: Wachhunde}\label{wachhunde}\index{Watchdog} -TODO!!! Auf der Lauer: Wachhunde +Es kommt vor, daß man einen Prozeß startet, bei dem man sich nicht sicher sein +kann daß er sich auch in absehbarer Zeit wieder beendet. Beispielsweise kann +der Timeout für einen Netzwerkzugriff deutlich höher liegen als erwünscht, und +wenn der `gegnerische' Dienst nicht antwortet bleibt einem nur zu warten. + +Es sei denn, man legt einen geeigneten Wachhund\footnote{Der englische Begriff +`Watchdog' ist in diesem Zusammenhang wahrscheinlich geläufiger...} auf die +Lauer, der im Notfall rettend eingreift. In einem Shell-Skript könnte das wie +folgt aussehen: + +\begin{lstlisting} +#!/bin/sh +timeout=5 +ping 192.168.0.254 & +cmdpid=$! +\end{lstlisting} + +Bis hierher nichts aufregendes. Eine Variable wird mit dem Timeout belegt, also +mit der Anzahl an Sekunden nach denen der zu überwachende Prozeß unterbrochen +werden soll. Dann wird der zu überwachende Prozeß gestartet und mittels \& in +den Hintergrund geschickt. Die Prozeß-ID des Prozesses wird in der Variablen +cmdpid gesichert. + +\begin{lstlisting}[firstnumber=5] +(sleep $timeout; kill -9 $cmdpid) & +watchdogpid=$! +\end{lstlisting} + +In Zeile 5 findet sich der eigentliche Watchdog. Hier wird eine Subshell +gestartet, in der zunächst der oben eingestellte Timeout abgewartet und dann +der zu überwachende Prozeß getötet wird. Diese Subshell wird ebenfalls mit \& +in den Hintergrund geschickt. Die ID der Subshell wird in der Variablen +watchdogpid gesichert. + +\begin{lstlisting}[firstnumber=7] +wait $cmdpid +kill $watchdogpid > /dev/null 2>&1 +exit 0 +\end{lstlisting} + +Dann wird durch ein \texttt{wait}\index{wait} darauf gewartet, daß sich der +überwachte Prozeß beendet. Dabei würde \texttt{wait} bis in alle Ewigkeit +warten, wäre da nicht der Watchdog in der Subshell. Wenn dem die Ausführung zu +lange dauert, sorgt er dafür daß der Prozeß beendet wird. + +Kommt der überwachte Prozeß aber rechtzeitig zurück, sorgt \texttt{kill} in +Zeile 8 dafür daß der Wachhund `eingeschläfert' wird. + +Auf diese Weise ist sichergestellt, daß der \texttt{ping} auf keinen Fall +länger als fünf Sekunden läuft. diff --git a/shell.tex b/shell.tex index a21b2c9..848084d 100644 --- a/shell.tex +++ b/shell.tex @@ -32,9 +32,9 @@ %\DeclareGraphicsRule{.tif}{bmp}{}{} % Grafikformate -\usepackage{tabularx} % für Tabellen über die Seitenbreite -\usepackage{longtable} % für Tabellen über die Seitenbreite -\usepackage{supertabular} % für Tabellen über die Seitenbreite +%\usepackage{tabularx} % für Tabellen über die Seitenbreite +%\usepackage{longtable} % für Tabellen über die Seitenbreite +%\usepackage{supertabular} % für Tabellen über die Seitenbreite \usepackage{ltxtable} % für Tabellen über die Seitenbreite \usepackage{makeidx} % Index wird später eingefügt @@ -44,7 +44,7 @@ \usepackage{fancybox} % Kästchen für Tastendarstellung -\usepackage{moreverb} % Für Listings +%\usepackage{moreverb} % Für Listings \usepackage{listings} % Für Listings \lstset{ extendedchars=true, @@ -69,7 +69,8 @@ bookmarksnumbered, linktocpage, colorlinks, - linkcolor = black + linkcolor = black, + urlcolor = black ]{hyperref} %\pdfinfo{/CreationDate (D:20000301170300-01'00')} % (D:YYYYMMDDhhmmss) @@ -104,14 +105,14 @@ \thispagestyle{empty} % eine Leerseite ~\vfill \footnotesize -Copyright \copyright{} 2000-2004 Ronald Schaten (\texttt{ronald@schatenseite.de})\bigskip +Copyright \copyright{} 2000-2005 Ronald Schaten (ronald@schatenseite.de)\bigskip TODO: Vernünftige Lizenz einsetzen!\bigskip %\texttt{${}$Id${}$}\bigskip Die aktuellste Version dieses Dokumentes befindet sich auf -\texttt{http://www.schatenseite.de/}.\bigskip +\href{http://www.schatenseite.de/}{http://www.schatenseite.de/}.\bigskip Dieses Dokument ist entstanden, weil ich für mich selbst eine kompakte Übersicht zu diesem Thema haben wollte. Ich beabsichtige nicht, damit in @@ -128,12 +129,12 @@ durch ihre konstruktive Kritik zu Verbesserungen beigetragen haben (in chronologischer Reihenfolge ihres Eingreifens): \begin{list}{$\bullet$}{\itemsep=-0.5cm} -\item Jürgen Ilse (\texttt{ilse@asys-h.de})\\ -\item Christian Perle (\texttt{christian.perle@tu-clausthal.de})\\ -\item Andreas Metzler (\texttt{ametzler@downhill.at.eu.org})\\ -\item Johannes Kolb (\texttt{johannes.kolb@web.de})\\ -\item Falk Friedrich (\texttt{falk@gmx.de})\\ -\item Kai Thöne (\texttt{kai.thoene@gmx.de})\\ +\item Jürgen Ilse (ilse@asys-h.de)\\ +\item Christian Perle (christian.perle@tu-clausthal.de)\\ +\item Andreas Metzler (ametzler@downhill.at.eu.org)\\ +\item Johannes Kolb (johannes.kolb@web.de)\\ +\item Falk Friedrich (falk@gmx.de)\\ +\item Kai Thöne (kai.thoene@gmx.de)\\ \end{list} Und ich bitte alle Leser, auf eventuelle Fehler zu achten und mich darauf diff --git a/werkzeugkasten.tex b/werkzeugkasten.tex index 4d378f1..485b39b 100644 --- a/werkzeugkasten.tex +++ b/werkzeugkasten.tex @@ -184,6 +184,7 @@ Verf \item \texttt{pgrep} (\ref{pgrep}): Bestimmte Prozesse suchen \item \texttt{pkill} (\ref{pkill}): Bestimmte Prozesse töten \item \texttt{trap} (\ref{trap}): Auf Signale reagieren +\item \texttt{wait} (\ref{wait}): Auf einen Prozeß warten \end{itemize} @@ -1632,6 +1633,22 @@ sogar ausschlie \index{uniq=\texttt{uniq}|)} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{wait}\label{wait}\index{wait=\texttt{wait}|(textbf} + +Das Kommando \texttt{wait} wird benutzt um auf die Beendigung eines Prozesses +zu warten. Als Parameter wird eine Prozeß-ID übergeben, \texttt{wait} läuft so +lange bis sich der Prozeß mit der angegebenen ID beendet hat. + +Wenn keine Prozeß-ID angegeben wurde, wartet \texttt{wait} auf alle +Kind-Prozesse der aktuellen Shell. + +Ein Beispiel für die Benutzung findet sich im Kapitel über Wachhunde (Abschnitt +\ref{wachhunde}). + +\index{wait=\texttt{wait}|)} + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{wc}\label{wc}\index{wc=\texttt{wc}|(textbf} diff --git a/wie_sieht_ein_shell_skript_aus.tex b/wie_sieht_ein_shell_skript_aus.tex index af6ac6c..2ae81ed 100644 --- a/wie_sieht_ein_shell_skript_aus.tex +++ b/wie_sieht_ein_shell_skript_aus.tex @@ -785,5 +785,5 @@ Gerade der Mechanismus mit dem Piping sollte nicht untersch Es ist mit den passenden Tools unter Unix möglich, eine ganze Audio-CD mit zwei Befehlen an der Kommandozeile zu duplizieren. Das erste Kommando veranlaßt, daß die TOC (Table Of Contents) der CD in die Datei cd.toc geschrieben wird. Das dauert nur wenige Sekunden. Die Pipe steckt im zweiten Befehl. Hier wird der eigentliche Inhalt der CD mit dem Tool `cdparanoia' ausgelesen. Da kein Dateiname angegeben wird, schreibt cdparanoia die Daten auf seine Standard-Ausgabe. Diese wird von dem Brennprogramm `cdrdao' übernommen und in Verbindung mit der TOC `on the fly' auf die CD geschrieben.\label{cdrdao}\nopagebreak \LTXtable{\textwidth}{tab_beisp_datenstroeme_cdparanoia.tex} -\index{<=\texttt{<}|)}\index{!>\&=\texttt{!>\&}|)}\index{!>\&-=\texttt{!>\&-}|)}\index{<\&=\texttt{<\&}|)}\index{<\&-=\texttt{<\&-}|)}\index{!>=\texttt{!>}|)}\index{!>!>=\texttt{!>!>}|)}\index{Pipe|)}\index{Dateideskriptor|)}\index{Standard-Eingabe|)}\index{Standard-Ausgabe|)}\index{Standard-Fehlerausgabe|)} +\index{<=\texttt{<}|)}\index{!>\&=\texttt{!>\&}|)}\index{!>\&-=\texttt{!>\&-}|)}\index{<\&=\texttt{<\&}|)}\index{<\&-=\texttt{<\&-}|)}\index{!>=\texttt{!>}|)}\index{!>!>=\texttt{!>!>}|)}\index{Pipe|)}\index{Dateideskriptor|)}\index{Standard-Eingabe|)}\index{Standard-Ausgabe|)}\index{Standard-Fehlerausgabe|)}\index{Here-Dokument|)textbf} \index{Datenströme|)}