2003-04-11 15:05:25 +00:00
|
|
|
|
% $Id$
|
2001-07-02 12:52:18 +00:00
|
|
|
|
\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<64> 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<74>en, neue Techniken
|
|
|
|
|
kennenzulernen. Au<41>erdem kann das Wissen <20>ber gewisse Techniken eine gro<72>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<42>cke}
|
|
|
|
|
|
2005-01-06 10:48:37 +00:00
|
|
|
|
Eine sogenannte Tar-Br<42>cke benutzt man, wenn eine oder mehrere Dateien zwischen
|
|
|
|
|
Rechnern <20>bertragen werden sollen, aber kein Dienst wie SCP oder FTP zur
|
|
|
|
|
Verf<EFBFBD>gung steht. Au<41>erdem hat die Methode den Vorteil, da<64> Benutzerrechte und
|
|
|
|
|
andere Dateiattribute bei der <20>bertragung erhalten
|
|
|
|
|
bleiben\footnote{Vorausgesetzt nat<61>rlich, da<64> der Benutzer auf der entfernten
|
|
|
|
|
Seite <20>ber die n<>tigen Rechte verf<72>gt.}.
|
|
|
|
|
|
|
|
|
|
Der Trick besteht darin, auf einer Seite der Verbindung etwas mit \texttt{tar}
|
|
|
|
|
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
|
2005-01-14 16:27:08 +00:00
|
|
|
|
als Archiv gegeben wird, benutzt es~--~je nach der gew<65>hlten Aktion~--~die
|
2005-01-06 10:48:37 +00:00
|
|
|
|
Standard-Ein- bzw. -Ausgabe. Diese kann an ein weiteres \texttt{tar} <20>bergeben
|
|
|
|
|
werden um wieder entpackt zu werden.
|
|
|
|
|
|
|
|
|
|
Ein Beispiel verdeutlicht diese Kopier-F<>higkeit:
|
|
|
|
|
|
2005-01-21 17:23:30 +00:00
|
|
|
|
\lstinline_tar cf - . | ( cd /tmp/backup; tar xf - )_
|
2005-01-06 10:48:37 +00:00
|
|
|
|
|
|
|
|
|
Hier wird zun<75>chst der Inhalt des aktuellen Verzeichnisses `verpackt'. Das
|
|
|
|
|
Resultat wird an die Standard-Ausgabe geschrieben. Auf der Empf<70>ngerseite der
|
|
|
|
|
Pipe wird eine Subshell ge<67>ffnet. Das ist notwendig, da das empfangende
|
|
|
|
|
\texttt{tar} in einem anderen Verzeichnis laufen soll. In der Subshell wird
|
|
|
|
|
zun<EFBFBD>chst das Verzeichnis gewechselt. Dann liest ein \texttt{tar} von der
|
|
|
|
|
Standard-Eingabe und entpackt alles was er findet. Sobald keine Eingaben mehr
|
|
|
|
|
kommen, beendet sich der Proze<7A> mitsamt der Subshell.
|
|
|
|
|
|
|
|
|
|
Am Ziel-Ort finden sich jetzt die gleichen Dateien wie am Quell-Ort.
|
|
|
|
|
|
|
|
|
|
Das lie<69>e sich lokal nat<61>rlich auch anders l<>sen. Man k<>nnte erst ein Archiv
|
|
|
|
|
erstellen, das dann an anderer Stelle wieder auspacken. Nachteil: Es mu<6D>
|
|
|
|
|
gen<EFBFBD>gend Platz f<>r das Archiv vorhanden sein. Denkbar w<>re auch ein in den Raum
|
2005-01-21 17:23:30 +00:00
|
|
|
|
gestelltes
|
|
|
|
|
|
|
|
|
|
\lstinline_cp -Rp * /tmp/backup_
|
|
|
|
|
|
|
|
|
|
Allerdings fehlen einem dabei mitunter n<>tzliche
|
|
|
|
|
\texttt{tar}-Optionen\footnote{Mit \texttt{-l} verl<72><6C>t \texttt{tar}
|
|
|
|
|
beispielsweise nicht das File-System. N<>tzlich wenn nur eine Partition
|
2005-01-06 10:48:37 +00:00
|
|
|
|
gesichert werden soll.}, und die oben erw<72>hnte Br<42>cke w<>re mit einem reinen
|
|
|
|
|
\texttt{cp} nicht m<>glich.
|
|
|
|
|
|
|
|
|
|
Eine Seite der Pipe kann n<>mlich auch ohne Probleme auf einem entfernten
|
|
|
|
|
Rechner `stattfinden'. Kommandos wie \texttt{ssh} oder \texttt{rsh} (letzteres
|
|
|
|
|
nur unter Vorsicht einsetzen!) schlagen die Br<42>cke zu einem anderen System,
|
|
|
|
|
dort wird entweder gepackt und versendet oder quasi die Subshell gestartet und
|
|
|
|
|
gelesen. Das sieht wie folgt aus:
|
|
|
|
|
|
2005-01-21 17:23:30 +00:00
|
|
|
|
\lstinline_ssh 192.168.2.1 tar clf - / | (cd /mnt/backup; tar xf - )_
|
2005-01-06 10:48:37 +00:00
|
|
|
|
|
|
|
|
|
Hier wird auf einem entfernten Rechner die Root-Partition verpackt, per SSH in
|
|
|
|
|
das lokale System geholt und lokal im Backup-Verzeichnis entpackt.
|
|
|
|
|
|
|
|
|
|
Der Weg in die andere Richtung ist ganz <20>hnlich:
|
|
|
|
|
|
2005-01-21 17:23:30 +00:00
|
|
|
|
\lstinline_tar cf - datei.txt | ssh 192.168.2.1 "(mkdir -p $PWD ;cd $PWD; tar xf -)"_
|
2005-01-06 10:48:37 +00:00
|
|
|
|
|
|
|
|
|
Hier wird die Datei verpackt und versendet. Eine Besonderheit gegen<65>ber dem
|
|
|
|
|
vorigen Beispiel bestehtdarin, da<64> das Zielverzeichnis bei Bedarf erstellt
|
|
|
|
|
wird, bevor die Datei dort entpackt wird. Zur Erkl<6B>rung: Die Variable
|
|
|
|
|
\texttt{\$PWD} wird, da sie nicht von Ticks `gesichert' wird, schon lokal durch
|
|
|
|
|
die Shell expandiert. An dieser Stelle erscheint also auf dem entfernten System
|
|
|
|
|
der Name des aktuellen Verzeichnisses auf dem lokalen System.
|
2004-11-12 12:07:32 +00:00
|
|
|
|
|
2001-07-02 12:52:18 +00:00
|
|
|
|
|
|
|
|
|
\section{Binaries inside}
|
|
|
|
|
|
2005-01-14 16:27:08 +00:00
|
|
|
|
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<54>gersystem an.
|
|
|
|
|
Shell-Skripte sind mit wenigen Einschr<68>nkungen plattformunabh<62>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~--~<7E>blicherweise bin<69>ren~--~Pakete auf das Zielsystem
|
|
|
|
|
gebracht?
|
|
|
|
|
|
|
|
|
|
Im Prinzip gibt es daf<61>r zwei unterschiedliche Verfahren:
|
2001-07-02 12:52:18 +00:00
|
|
|
|
|
|
|
|
|
\subsection{Bin<EFBFBD>re Here-Dokumente}
|
2005-01-14 16:27:08 +00:00
|
|
|
|
\index{Here-Dokument}
|
|
|
|
|
|
|
|
|
|
Eine M<>glichkeit ist es, die bin<69>re Datei in Form eines Here-Dokuments
|
|
|
|
|
mitzuliefern. Da es aber in der Natur einer bin<69>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<64> 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<6D> an der
|
|
|
|
|
Stelle auf jeden Fall sichergestellt werden, da<64> keine existierenden Dateien
|
|
|
|
|
versehentlich <20>berschrieben werden.
|
|
|
|
|
|
|
|
|
|
Um diesen Abschnitt nicht allzu lang werden zu lassen <20>berspringen wir einen
|
|
|
|
|
Teil der Datei.
|
2001-07-02 12:52:18 +00:00
|
|
|
|
|
2005-01-14 16:27:08 +00:00
|
|
|
|
\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} <20>berpr<70>ft und
|
|
|
|
|
im Fehlerfall eine Ausgabe gemacht. Im Erfolgsfall wird das Bild mittels
|
|
|
|
|
\texttt{display} angezeigt.
|
2001-07-02 12:52:18 +00:00
|
|
|
|
|
|
|
|
|
\subsection{Schwanz ab!}
|
|
|
|
|
|
2005-01-14 16:27:08 +00:00
|
|
|
|
Diese Variante basiert darauf, da<64> die bin<69>re Datei ohne weitere Codierung an
|
|
|
|
|
das Shell-Skript angeh<65>ngt wurde. Nachteil dieses Verfahrens ist, da<64> das
|
|
|
|
|
`abschneidende Kommando' nach jeder <20>nderung der L<>nge des Skriptes angepa<70>t
|
|
|
|
|
werden mu<6D>.
|
|
|
|
|
|
|
|
|
|
Dabei gibt es zwei Methoden, die angeh<65>ngte Datei wieder abzuschneiden. Die
|
|
|
|
|
einfachere Methode funktioniert mit \texttt{tail}:
|
|
|
|
|
|
|
|
|
|
\texttt{tail -n +227 \$0 > icon.png}
|
|
|
|
|
|
|
|
|
|
Dieses Beispiel geht davon aus, da<64> das Skript selbst 227 Zeilen umfa<66>t. Die
|
|
|
|
|
bin<EFBFBD>re Datei wurde mit einem Kommando wie \texttt{cat icon.png >> skript.sh} an
|
|
|
|
|
das Skript angeh<65>ngt.
|
|
|
|
|
|
|
|
|
|
F<EFBFBD>r die etwas kompliziertere Variante mu<6D> die L<>nge des eigentlichen
|
|
|
|
|
Skript-Teiles genau angepa<70>t werden. Wenn das Skript beispielsweise etwa 5,5kB
|
|
|
|
|
lang ist, m<>ssen genau passend viele Leerzeilen oder Kommentarzeichen angeh<65>ngt
|
|
|
|
|
werden, damit sich eine L<>nge von 6kB ergibt. Dann kann das Anh<6E>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<67><72>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<69>lich
|
|
|
|
|
wird festgelegt, da<64> bei der Aktion die ersten sechs Block~--~also die ersten
|
|
|
|
|
sechs Kilobytes~--~<7E>bersprungen werden sollen.
|
|
|
|
|
|
|
|
|
|
Um es nochmal zu betonen: Diese beiden Methoden sind mit Vorsicht zu genie<69>en.
|
|
|
|
|
Bei der ersten f<>hrt jede zus<75>tzliche oder gel<65>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.
|
2001-07-02 12:52:18 +00:00
|
|
|
|
|
|
|
|
|
\section{Dateien, die es nicht gibt}
|
|
|
|
|
|
2004-11-05 16:20:53 +00:00
|
|
|
|
TODO!!! Dateien, die es nicht gibt
|
2001-07-02 12:52:18 +00:00
|
|
|
|
|
|
|
|
|
\subsection{Speichern in nicht existente Dateien}
|
|
|
|
|
|
2004-11-05 16:20:53 +00:00
|
|
|
|
TODO!!! Speichern in nicht existente Dateien
|
2001-07-02 12:52:18 +00:00
|
|
|
|
|
2004-12-02 13:54:06 +00:00
|
|
|
|
\subsection{Subshell-Schleifen vermeiden}\label{subshellschleifen}
|
2002-03-25 13:48:40 +00:00
|
|
|
|
|
|
|
|
|
Wir wollen ein Skript schreiben, das die \texttt{/etc/passwd} liest und dabei
|
|
|
|
|
z<EFBFBD>hlt, wie viele Benutzer eine UID kleiner als 100 haben.
|
|
|
|
|
|
|
|
|
|
Folgendes Skript funktioniert nicht:
|
|
|
|
|
|
2004-12-10 14:38:03 +00:00
|
|
|
|
\begin{lstlisting}
|
2002-03-25 13:48:40 +00:00
|
|
|
|
#!/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
|
2004-12-10 14:38:03 +00:00
|
|
|
|
\end{lstlisting}
|
2002-03-25 13:48:40 +00:00
|
|
|
|
|
|
|
|
|
Was ist passiert?
|
|
|
|
|
|
|
|
|
|
Dieses Skript besteht im Wesentlichen aus einer Pipe. Wir haben ein
|
2004-11-19 12:09:34 +00:00
|
|
|
|
\texttt{cat}-Kom\-man\-do, das den Inhalt der \texttt{/etc/passwd} durch eben
|
|
|
|
|
diese Pipe an eine Schleife <20>bergibt. Das \texttt{read}-Kommando in der
|
|
|
|
|
Schleife liest die einzelnen Zeilen aus, dann folgt ein Bi<42>chen Auswertung.
|
2002-03-25 13:48:40 +00:00
|
|
|
|
|
|
|
|
|
Es ist zu beobachten, da<64> bei der Ausgabe in Zeile 7 die Variable
|
|
|
|
|
\texttt{\$count} korrekte Werte enth<74>lt. Um so unverst<73>ndlicher ist es, da<64> sie
|
|
|
|
|
nach der Vollendung der Schleife wieder den Wert 0 enth<74>lt.
|
|
|
|
|
|
|
|
|
|
Das liegt daran, da<64> diese Schleife als Teil einer Pipe in einer Subshell
|
|
|
|
|
ausgef<EFBFBD>hrt wird. Die Variable \texttt{\$count} steht damit in der Schleife
|
|
|
|
|
praktisch nur lokal zur Verf<72>gung, sie wird nicht an das umgebende Skript
|
2004-11-05 16:20:53 +00:00
|
|
|
|
`hochgereicht'.
|
2002-03-25 13:48:40 +00:00
|
|
|
|
|
|
|
|
|
Neben der Methode in \ref{daten_hochreichen} bietet sich hier eine viel
|
|
|
|
|
einfachere L<>sung an:
|
|
|
|
|
|
2004-12-10 14:38:03 +00:00
|
|
|
|
\begin{lstlisting}
|
2002-03-25 13:48:40 +00:00
|
|
|
|
#!/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
|
2004-12-10 14:38:03 +00:00
|
|
|
|
\end{lstlisting}
|
2002-03-25 13:48:40 +00:00
|
|
|
|
|
|
|
|
|
Hier befindet sich die Schleife nicht in einer Pipe, daher wird sie auch nicht
|
|
|
|
|
in einer Subshell ausgef<65>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}
|
2001-07-02 12:52:18 +00:00
|
|
|
|
|
2005-01-14 16:27:08 +00:00
|
|
|
|
Ein immer wieder auftretendes und oft sehr verwirrendes Problem ist, da<64>
|
|
|
|
|
Variablen die in einer Subshell definiert wurden au<61>erhalb dieser nicht
|
|
|
|
|
sichtbar sind (siehe Abschnitt \ref{subshellschleifen}). Dies ist um so
|
|
|
|
|
<EFBFBD>rgerlicher, als da<64> Subshells auch bei vergleichsweise einfachen Pipelines
|
|
|
|
|
ge<EFBFBD>ffnet werden.
|
|
|
|
|
|
|
|
|
|
Ein Beispiel f<>r ein mi<6D>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<65>hlt. Am Ende der Schleife enth<74>lt die
|
|
|
|
|
Variable tats<74>chlich den korrekten Wert, aber da die Pipe eine Subshell
|
|
|
|
|
ge<EFBFBD>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<6E>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<6F>re Datei geschrieben werden, die
|
|
|
|
|
danach geparst werden m<><6D>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 <20>berlegung kommen wir zu einem eleganten~--~wenn auch
|
|
|
|
|
nicht ganz einfachen~--~Trick.
|
|
|
|
|
|
|
|
|
|
Anstatt die Daten in eine tempor<6F>re Datei zu schreiben, wo sie wom<6F>glich durch
|
|
|
|
|
andere Prozesse ver<65>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<75>chst eine tempor<6F>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<65>hlt wie im ersten Beispiel. Allerdings wurde explizit eine Subshell
|
|
|
|
|
um die Schleife gestartet. Dadurch steht die in der Schleife hochgez<65>hlte
|
|
|
|
|
Variable auch nach Beendigung der Schleife zur Verf<72>gung, allerdings immernoch
|
|
|
|
|
nur in der Subshell. Um das zu <20>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<64> der Wert von nCounter
|
|
|
|
|
tats<EFBFBD>chlich au<61>erhalb der Subshell nicht zur Verf<72>gung steht. Zun<75>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<EFBFBD>end zeigt die Zeile 15, da<64> der Transport tats<74>chlich funktioniert
|
|
|
|
|
hat, die Variable nCounter ist mit dem Wert aus der Subshell belegt.
|
|
|
|
|
|
2002-03-22 16:34:33 +00:00
|
|
|
|
|
|
|
|
|
\subsection{Dateien gleichzeitig lesen und schreiben}
|
|
|
|
|
|
|
|
|
|
Es kommt vor, da<64> man eine Datei bearbeiten m<>chte, die hinterher aber wieder
|
|
|
|
|
unter dem gleichen Namen zur Verf<72>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
|
|
|
|
|
|
2004-12-10 14:38:03 +00:00
|
|
|
|
\lstinline|grep wichtig datei.txt > datei.txt|
|
2002-03-22 16:34:33 +00:00
|
|
|
|
|
|
|
|
|
sein. Das kann funktionieren, es kann aber auch in die sprichw<68>rtliche Hose
|
|
|
|
|
gehen. Das Problem an der Stelle ist, da<64> die Datei an der Stelle gleichzeitig
|
|
|
|
|
zum Lesen und zum Schreiben ge<67>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:
|
|
|
|
|
|
2004-12-10 14:38:03 +00:00
|
|
|
|
\begin{lstlisting}
|
2002-03-22 16:34:33 +00:00
|
|
|
|
#!/bin/sh
|
|
|
|
|
FILE=datei.txt
|
|
|
|
|
exec 3< "$FILE"
|
|
|
|
|
rm "$FILE"
|
|
|
|
|
grep "wichtig" <&3 > "$FILE"
|
2004-12-10 14:38:03 +00:00
|
|
|
|
\end{lstlisting}
|
2002-03-22 16:34:33 +00:00
|
|
|
|
|
2002-03-25 13:48:40 +00:00
|
|
|
|
Allerdings sollte man bei dieser Methode beachten, da<64> man im Falle eines
|
|
|
|
|
Fehlers die Quelldaten verliert, da die Datei ja bereits gel<65>scht wurde.
|
2004-11-05 16:20:53 +00:00
|
|
|
|
|
2005-01-14 16:27:08 +00:00
|
|
|
|
\section{Auf der Lauer: Wachhunde}\label{wachhunde}\index{Watchdog}
|
|
|
|
|
|
|
|
|
|
Es kommt vor, da<64> man einen Proze<7A> startet, bei dem man sich nicht sicher sein
|
|
|
|
|
kann da<64> er sich auch in absehbarer Zeit wieder beendet. Beispielsweise kann
|
|
|
|
|
der Timeout f<>r einen Netzwerkzugriff deutlich h<>her liegen als erw<72>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<65>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 <20>berwachende Proze<7A> unterbrochen
|
|
|
|
|
werden soll. Dann wird der zu <20>berwachende Proze<7A> gestartet und mittels \& in
|
|
|
|
|
den Hintergrund geschickt. Die Proze<7A>-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<75>chst der oben eingestellte Timeout abgewartet und dann
|
|
|
|
|
der zu <20>berwachende Proze<7A> get<65>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<64> sich der
|
|
|
|
|
<EFBFBD>berwachte Proze<7A> beendet. Dabei w<>rde \texttt{wait} bis in alle Ewigkeit
|
|
|
|
|
warten, w<>re da nicht der Watchdog in der Subshell. Wenn dem die Ausf<73>hrung zu
|
|
|
|
|
lange dauert, sorgt er daf<61>r da<64> der Proze<7A> beendet wird.
|
|
|
|
|
|
|
|
|
|
Kommt der <20>berwachte Proze<7A> aber rechtzeitig zur<75>ck, sorgt \texttt{kill} in
|
|
|
|
|
Zeile 8 daf<61>r da<64> der Wachhund `eingeschl<68>fert' wird.
|
2004-11-05 16:20:53 +00:00
|
|
|
|
|
2005-01-14 16:27:08 +00:00
|
|
|
|
Auf diese Weise ist sichergestellt, da<64> der \texttt{ping} auf keinen Fall
|
|
|
|
|
l<EFBFBD>nger als f<>nf Sekunden l<>uft.
|
2004-11-05 16:20:53 +00:00
|
|
|
|
|