Diverse Aenderungen.
This commit is contained in:
@@ -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<R_S]<P/F7AXDA'I\>@``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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user