![]() |
Praktikum "UNIX"von Prof. Jürgen Plate |
Das Shell-Skript wird mit einem Editor erstellt und kann alle Möglichkeiten der Shell nutzen, die auch bei der interaktiven Eingabe möglich sind. Insbesondere kann auch die Umleitung der Ein-/Ausgabe wie bei einem Binärprogramm erfolgen. Selbstverständlich lassen sich auch innerhalb eines Skripts weitere Shell-Skripten aufrufen. Zusätzlich kennt die Shell einige interne Befehle. Dem Shell-Skript können Parameter übergeben werden, es ist damit universeller verwendbar.
Merke: Durch Testen kann nur die Fehlerhaftigkeit von Skripts nachgewiesen, aber nicht deren Korrektheit bewiesen werden.
Bei vielen Skripts findet man eine Sonderform der Kommentarzeile zu Beginn, die beispielsweise so aussieht:
#!/bin/sh
Durch diesen Kommentar wird festgelegt, welches Programm für die Ausführung des Skripts verwendet wird. Er wird hauptsächlich bei Skripts verwendet, die allen Benutzern zur Verfügung stehen sollen. Da möglicherweise unterschiedliche Shells verwendet werden, kann es je nach Shell (sh, csh, ksh,...) zu Syntaxfehlern bei der Ausführung des Skripts kommen. Durch die Angabe der ausführenden Shell wird dafür gesorgt, daß nur die "richtige" Shell verwendet wird. Die Festlegung der Shell stellt außerdem einen Sicherheitsmechanismus dar, denn es könnte ja auch ein Benutzer eine modifizierte Shell verwenden. Neben den Shells können auch andere Programme zur Ausführung des Skripts herangezogen werden; häufig sind awk- und perl-Skripts.
Beispiele zur Verdeutlichung des Sachverhaltes:
Form | Beispiel | Ausgabe |
Deklaration | $ foo= | |
Wertzuweisung | $ foo=bar | |
Wertreferenzierung | $ echo $foo | bar |
Im Allgemeinen werden Variablen in der Shell nicht explizit deklariert. Vielmehr ist der Wertzuweisung die Variablendeklaration implizit enthalten. Wird eine Variable dennoch ohne Wertzuweisung deklariert, so wird bei der Wertreferenzierung ein leerer String ("") zurückgegeben.
Für Variablen gilt allgemein:
VAR=Wert | Wert ist ein String (ohne Leerzeichen und Sonderzeichen) |
VAR="Wert" | Wert ist ein String (Ersetzung eingeschränkt, Leerzeichen und Sonderzeichen dürfen enthalten sein) |
VAR=`kommando` VAR=$(kommando) |
Wert der Variablen ist die Ausgabe des Kommandos (Newline --> Leerzeichen) |
$ VAR="Hello World!" $ echo $VAR Hello World! $ echo '$VAR' $VAR $ VAR=`pwd` $ echo "Aktuelles Verzeichnis: $VAR" Aktuelles Verzeichnis: /home/plate $ VAR=`pwd` $ echo ${VAR}/bin /home/plate/bin $ VAR=/usr/tmp/mytmp $ ls > $VARDas letzte Beispiel schreibt die Ausgabe von ls in die Datei /usr/tmp/mytmp
Enthält eine Variable ein Kommando, so kann dies Kommando durch Angabe der Variablen ausgeführt werden, z. B.:
$ VAR="ls -la" $ $VAR
\ | entwertet nachfolgendes Metazeichen |
' ' | entwertet alle dazwischenliegenden Metazeichen echo '$VAR' liefert als Ausgabe $VAR |
" " | entwertet alle dazwischenliegenden Metazeichen, aber nicht die
Variablen- und Kommandosubstitution echo "$VAR" liefert abcdef |
Variable | Bedeutung |
HOME | Home-Directory (absoluter Pfad) |
PATH | Suchpfad für Kommandos und Skripts |
MANPATH | Suchpfad für die Manual-Seiten |
Mail-Verzeichnis | |
SHELL | Name der Shell |
LOGNAME USER | Login-Name des Benutzers |
PS1 | System-Prompt ($ oder #) |
PS2 | Prompt für Anforderung weiterer Eingaben (>) |
IFS | (internal field separator) Trennzeichen, meist CR, Leerzeichen und Tab) |
TZ | Zeitzone (z. B. MEZ) |
Folgende spezielle Variablen sind definiert:
Variable | Bedeutung | Kommandobeispiel |
$- | gesetzte Shell-Optionen | set -xv |
$$ | PID (Prozeßnummer) der Shell | kill -9 $$ (Selbstmord) |
$! | PID des letzten Hintergrundprozesses | kill -9 $! (Kindermord) |
$? | Exitstatus des letzten Kommandos | cat /etc/passwd ; echo $? |
Positionsparameter | Bedeutung |
$# | Anzahl der Argumente |
$0 | Name des Kommandos |
$1 | 1. Argument |
. . . . |
. . . . |
$9 | 9. Argument |
$@ | alle Argumente (z. B. für Weitergabe an Subshell) |
$* | alle Argumente konkateniert (--> ein einziger String) |
Zur Verdeutlichung soll ein kleines Beispiel-Shell-Skript dienen:
#!/bin/sh
echo "Mein Name ist $0"
echo "Mir wurden $# Parameter übergeben"
echo "1. Parameter = $1"
echo "2. Parameter = $2"
echo "3. Parameter = $3"
echo "Alle Parameter zusammen: $*"
echo "Meine Prozeßnummer PID = $$"
Nachdem dieses Shell-Skript mit einem Editor erstellt wurde, muß es noch ausführbar gemacht werden (chmod u+x foo). Anschließend wird es gestartet und erzeugt die folgenden Ausgaben auf dem Bildschirm:
$ ./foo eins zwei drei vier
Mein Name ist ./foo
Mir wurden 4 Parameter übergeben
1. Parameter = eins
2. Parameter = zwei
3. Parameter = drei
Alle Parameter zusammen: eins zwei drei vier
Meine Prozeßnummer PID = 3212
$
Anmerkung: So, wie Programme und Skripts des UNIX-Systems in Verzeichnissen wie /bin oder /usr/bin zusammengefaßt werden, ist es empfehlenswert, im Home-Directory ein Verzeichnis bin einzurichten, das Programme und Skripts aufnimmt. Die Variable PATH wird dann in der Datei .profile durch die Zuweisung PATH=$PATH:$HOME/bin erweitert. Damit die Variable PATH auch in Subshells (d. h. beim Aufruf von Skripts) auch wirksam wird, muß sie exportiert werden:
export PATH
Alle exportierten Variablen bilden das Environment für die Subshells. Information darüber erhält man mit dem Kommandos:
set: Anzeige von Shellvariablen und Environmentvariablen
oder
env: Anzeige der Environmentvariablen
In Shellskripts kann es sinnvoll sein, die Variablen in Anführungszeichen ("...") zu setzen, um Fehler zu verhindern. Beim Aufruf müssen Parameter, die Sonderzeichen enthalten ebenfalls in Anführungszeichen (am besten '...') gesetzt werden. Dazu ein Beispiel. Das Skript "zeige" enthält folgende Zeile:
grep $1 dat.adr
Der Aufruf zeige 'Hans Meier' liefert nach der Ersetzung das fehlerhafte Kommando grep Hans Meier dat.adr, das nach dem Namen 'Hans' in der Adreßdatei dat.adr und einer (vermutlich nicht vorhandenen) Datei namens 'Meier' sucht. Die Änderung von "zeige":
grep "$1" dat.adr
liefert bei gleichem Parameter die korrekte Version grep "Hans Meier" dat.adr. Die zweite Quoting-Alternative grep '$1' dat.adr ersetzt den Parameter überhaupt nicht und sucht nach der Zeichenkette "$1". Das Skript "zeige" soll nun enthalten:
echo "Die Variable XXX hat den Wert $XXX"
Nun wird eingegeben:
$ XXX=Test $ zeige
Als Ausgabe erhält man:
Die Variable XXX hat den Wert
Erst wenn die Variable "exportiert" wird, erhält man das gewünschte Ergebnis:
$ XXX=Test $ export XXX $ zeige Die Variable XXX hat den Wert Test
Das Skript "zeige" enthalte nun die beiden Kommandos:
echo "zeige wurde mit $# Parametern aufgerufen:" echo "$*"
Die folgenden Kommandoaufrufe zeigen die Behandlung unterschiedlicher Parameter:
$ zeige zeige wurde mit 0 Parametern aufgerufen: $zeige eins zwei 3 zeige wurde mit 3 Parametern aufgerufen: eins zwei 3 $ zeige eins "Dies ist Parameter 2" drei zeige wurde mit 3 Parametern aufgerufen: eins Dies ist Parameter 2 drei
Die Definition von Variablen und Shell-Funktionen (siehe später) kann man mit unsetwieder Rückgängig machen.
Am besten läßt sich das am Beispielen zeigen, für die folgende Vorbesetzungen gelten:
einfache Substitution | $W1 | HELLO |
String-Konkatenation | ${W1}HaHa | HelloHaHa |
bedingte Substitution | ${W1-"is nich!"} ${W2-"is nich!"} |
Hello is nich! |
falls Variable undefiniert ist, nimm Parameter 1 | ${W1-$1} ${W2-$1} |
Hello abc |
falls Variable undefiniert, nimm $1 und brich Skript ab | ${W1?$1} ${W2?$1} |
Hello abc <Abbruch> |
falls Variable definiert, nimm $1, sonst nichts | ${W1+$1} ${W2+$1} | abc |
In Kommandodateien können Variablen auch Kommandonamen oder -aufrufe enthalten, da ja die Substitution vor der Ausführung erfolgt.
shift
Eliminieren von $1, $2 ... $n --> $1 ... $n-1
Die Prozedur "zeige" enthält folgende Befehle:
echo "$# Argumente:" echo "$*" shift echo "Nach shift:" echo "$# Argumente:" echo "$*"
Der folgende Aufruf von "zeige" liefert:
$ zeige eins zwei drei 3 Argumente: eins zwei drei Nach shift: 2 Argumente: zwei drei
shift wird jedoch viel häufiger verwendet, wenn die Zahl der Parameter variabel ist. Es wird dann in einer Schleife so lange mit shift gearbeitet, bis die Anzahl der Parameter 0 ist:
while [ $# -gt 0 ] do tuwas mit $1 shift done
Die Kommunikation mit dem Elternprozeß kann aber z. B. mit Dateien erfolgen. Bei kleinen Dateien spielt sich fast immer alles im Cache, also im Arbeitsspeicher ab und ist somit nicht so ineffizient, wie es zunächst den Anschein hat. Außerdem liefert jedes Programm einen Rückgabewert, der vom übergeordneten Prozeß ausgewertet werden kann.
Es gibt außerdem ein Kommando, das ein Shell-Skript in der aktuellen Shell und nicht in einer Subshell ausführt:
. (Dot) Skript ausführen
Das Dot-Kommando erlaubt die Ausführung eines Skripts in der aktuellen Shell-Umgebung, z. B. das Setzen von Variablen usw.
Damit die Variable auch in Subshells (d. h. beim Aufruf von Skripts auch wirksam wird, muß sie exportiert werden:
export PATH
Alle exportierten Variablen bilden das Environment für die Subshells.
read variable [variable ...]
read liest eine Zeile von der Standardeingabe und weist die einzelnen Felder den angegebenen Variablen zu. Feldtrenner sind die in IFS definierten Zeichen. Sind mehr Variablen als Eingabefelder definiert, werden die überzähligen Felder mit Leerstrings besetzt. Umgekehrt nimmt die letzte Variable den Rest der Zeile auf. Wird im Shell-Skript die Eingabe mit < aus einer Datei gelesen, bearbeitet read die Datei zeilenweise.
Anmerkung: Da das Shell-Skript in einer Sub-Shell läuft, kann IFS im Skript umdefiniert werden, ohne daß es nachher restauriert werden muß. Die Prozedur "zeige" enthält beispielsweise folgende Befehle:
IFS=',' echo "Bitte drei Parameter, getrennt durch Komma eingeben:" read A B C echo Eingabe war: $A $B $C
Aufruf (Eingabe kursiv):
$ zeige Bitte drei Parameter, getrennt durch Komma eingeben: eins,zwei,drei Eingabe war: eins zwei drei
Eingeleitet werden Hier-Dokumente mit << und anschließend einer Zeichenfolge, die das Ende des Hier-Dokuments anzeigt. Diese Zeichenfolge steht dann alleine am Anfang einer neuen Zeile (und gehört nicht mehr zum Hier-Dokument). Bei Quoting der Ende-Zeichenfolge (eingeschlossen in "...", '...'), werden die Datenzeilen von den üblichen Ersetzungsmechanismen ausgeschlossen. Dazu ein Beispiel:
Die Shell-Skript "hier" enthält folgende Zeilen:
cat << EOT Dieser Text wird ausgegeben, als ob er von einer externen Datei kaeme - na ja, nicht ganz so. Die letzte Zeile enthaelt nur das EOT und wird nicht mit ausgegeben. Die folgende Zeile wuerde bei der Eingabe aus einer Datei nicht ersetzt. Parameter: $* EOT
Aufruf:
$ hier eins zwei Dieser Text wird ausgegeben, als ob er von einer externen Datei kaeme - na ja, nicht ganz so. Die letzte Zeile enthaelt nur das EOT und wird nicht mit ausgegeben. Die folgende Zeile wuerde bei der Eingabe aus einer Datei nicht ersetzt. Parameter: eins zwei
Außerden wäre bei der Eingabe aus einer Datei die Ersetzung von '$*' durch die aktuellen Parameter nicht möglich. Hier-Dokumente bieten also weitere Vorteile. Diese Vorteile lassen sich besonders gut ausspielen, wenn man eine Datei mit ed bearbeitet und dabei die Editor-Kommandos als Hier-Dokument mitgibt. Durch in die Kommandos eingestreute Variablen wird die ganze Dateibearbeitung auch variabel steuerbar.
Noch ein Beispiel, diesmal die Simulation des Kommandos 'wall'.
for X in `who | cut -d' ' -f1` do write $X << TEXTENDE Hallo Leute, das Wetter ist schoen. Wollen wir da nicht um 17 Uhr Schluss machen und in den Biergarten gehen? TEXTENDE done
Die Variablenersetzung oder andere Substitutionen im Text finden nur statt, wenn das Wort hinter dem Umleitungsoperator keine Quotation (einfache oder doppelte Anführungszeichen, Backslash) enthält. Text und Endmarkierung lassen sich leider nicht einrücken: Führende Leer- oder Tabulatorzeichen würden Bestandteil des Textes, und eine eingerückte Endmarkierung erkennt die Shell nicht. Ohne Einrückungen hingegen sind Skripte oft schlechter lesbar. Die Boume-Shell verfügt deshalb über den zweiten Operator <<-, der Tabulatorzeichen am Zeilenanfang ignoriert.
kommando1 ; kommando2; kommando3
Kommando1 && Kommando2
Für die Bewertung der Abarbeitung wird folgende Wahrheitstabelle verwendet:
Exitstatus Kommando 1 | Exitstatus Kommando 2 | &&-Verkettung |
---|---|---|
Exitstatus gleich 0 --> ok | Exitstatus gleich 0 --> ok | Exitstatus gleich 0 --> ok |
Exitstatus gleich 0 --> ok | Exitstatus ungleich 0 --> nicht ok | Exitstatus ungleich 0 --> nicht ok |
Exitstatus ungleich 0 --> nicht ok | wird nicht gestartet! | Exitstatus ungleich 0 --> nicht ok |
Kommando1 || Kommando2
Für die Bewertung der Abarbeitung wird folgende Wahrheitstabelle verwendet:
Exitstatus Kommando 1 | Exitstatus Kommando 2 | ||-Verkettung |
---|---|---|
Exitstatus gleich 0 --> ok | wird nicht gestartet! | Exitstatus gleich 0 --> ok |
Exitstatus ungleich 0 --> nicht ok | Exitstatus gleich 0 --> ok | Exitstatus gleich 0 --> ok |
Exitstatus ungleich 0 --> nicht ok | Exitstatus ungleich 0 --> nicht ok | Exitstatus ungleich 0 --> nicht ok |
pwd > out ; who >> out ; ls >> out
Die Umleitung läßt sich auch auf die Fehlerausgabe erweitern
echo "Fehler!" # geht nach stdout
echo "Fehler!" 1>&2 # geht nach stderr
Kommandos lassen sich zur gemeinsamen E/A-Umleitung mit {...} klammern:
{ Kommando1 ; Kommando2 ; Kommando3 ; ... ; }
Wichtig: Die geschweiften Klammern müssen von Leerzeichen eingeschlossen werden!
Die Ausführung der Kommandos erfolgt nacheinander innerhalb der aktuellen Shell, die Ausgabe kann gemeinsam umgelenkt werden, z. B.:
{ pwd ; who ; ls ; } > out
Die schließende Klammer muß entweder durch einen Strichpunkt vom letzen Kommando getrennt werden oder am Beginn einer neuen Zeile stehen. Die geschweiften Klammern sind ein ideales Mittel, die Ausgabe aller Programme eines Shell-Skripts gemeinsam umzuleiten, wenn das Skript beispielsweise mit 'nohup' oder über cron gestartet wird. Man fügt lediglich am Anfang eine Zeile mit der öffnenden Klammer ein und am Schluß die gewünschte Umleitung, z. B.:
{ ... ... ... ... } | mailx -s "Output from Foo" $LOGNAME
Eine Folge von Kommandos kann aber auch in einer eigenen Subshell ausgeführt werden:
( Kommando1 ; Kommando2 ; Kommando3 ; ... )
Das Ergebnis des letzten ausgeführten Kommados wird als Ergebnis der Klammer zurückgegeben. Auch hier kann die Umleitung der Standardhandles gemeinsam erfolgen. Auch dazu ein Beispiel. Bei unbewachten Terminals "bastle" ich gerne an der Datei '.profile' des Users. Eine Zeile
( sleep 300 ; audioplay /home/local/sounds/telefon.au ) &
ist schnell eingebaut. Fünf Minuten nach dem Login rennt dann jemand zum Telefon (geht natürlich nur, wenn der Computer auch soundfähig ist). Noch gemeiner wäre
( sleep 300 ; kill -9 0 ) &
Abschließend vielleicht noch etwas Nützliches. Wenn Sie feststellen daß eine Plattenpartition zu klein geworden ist, müssen Sie nach Einbau und Formatierung einer neuen Platte oftmals ganze Verzeichnisbäme von der alten Platte auf die neue kopieren. Auch hier hilft die Kommandoverkettung zusammen mit dem Programm tar (Tape ARchive), das es nicht nur erlaubt, einen kompletten Verzeichnisbaum auf ein Gerät, etwa einen Streamer, zu kopieren, sondern auch in eine Datei oder auf die Standardausgabe. Wir verknüpfen einfach zwei tar-Prozesse, von denen der erste das Verzeichnis archiviert und der zweite über eine Pipe das Archiv wieder auspackt. Der Trick am ganzen ist, das beide Prozesse in verschiedenen Verzeichnissen arbeiten. Angenommen wir wollen das Verzeichnis /usr/local nach /mnt kopieren:
( cd /usr/local ; tar cf - . ) | ( cd /mnt ; tar xvf - )
Der Parameter "f" weist tar an, auf eine Datei zu schreiben oder von einer Datei zu lesen. Hat die Datei wie oben den Namen "-", handelt es sich um stdout bzw. stdin.
test Argument
Dieses Kommando prüft eine Bedingung und liefert 'true' (0), falls die Bedingung erfüllt ist und 'false' (1), falls die Bedingung nicht erfüllt ist. Der Fehlerwert 2 wird zurückgegeben, wenn das Argument syntaktisch falsch ist (meist durch Ersetzung hervorgerufen). Es lassen sich Dateien, Zeichenketten und Integer-Zahlen (16 Bit, bei Linux 32 Bit) überprüfen.
Das Argument von Test besteht aus einer Testoption und einem Operanden, der ein Dateiname oder eine Shell-Variable (Inhalt: String oder Zahl) sein kann. In bestimmten Fällen können auf der rechten Seite eines Vergleichs aus Strings oder Zahlen stehen - bei der Ersetzung von leeren Variablen kann es aber zu Syntaxfehlern kommen. Weiterhin lassen sich mehrere Argumente logisch verknüpfen (UND, ODER, NICHT). Beispiel:
test -w /etc/passwd
mit der Kommandoverkettung lassen sich so schon logische Entscheidungen treffen, z. B.:
test -w /etc/passwd && echo "Du bist ROOT"
Normalerweise kann statt 'test' das Argument auch in eckigen Klammern gesetzt werden. Die Klammern müssen von Leerzeichen umschlossen werden:
[ -w /etc/passwd ]
Die folgenden Operationen können bei 'test' bzw. [ ... ] verwendet werden.
Ausdruck | Bedeutung |
-e < datei > | datei existiert |
-r < datei > | datei existiert und Leserecht |
-w <datei> | datei existiert und Schreibrecht |
-x <datei> | datei existiert und Ausführungsrecht |
-f <datei> | datei existiert und ist einfache Datei |
-d <datei> | datei existiert und ist Verzeichnis |
-h <datei> | datei existiert und ist symbolisches Link |
-c <datei> | datei existiert und ist zeichenor. Gerät |
-b <datei> | datei existiert und ist blockor. Gerät |
-p <datei> | datei existiert und ist benannte Pipe |
-u <datei> | datei existiert und für Eigentümer s-Bit gesetzt |
-g <datei> | datei existiert und für Gruppe s-Bit gesetzt |
-k <datei> | datei existiert und t- oder sticky-Bit gesetzt |
-s <datei> | datei existiert und ist nicht leer |
-L <datei> | datei ist symbolisches Link |
-t <dateikennzahl> | dateikennzahl ist einem Terminal zugeordnet |
Vergleich von Zeichenketten | |
---|---|
Ausdruck | Bedeutung |
-n <String> | wahr, wenn String nicht leer |
-z <String> | wahr, wenn String leer ist |
<String1> = <String2> | wahr, wenn die Zeichenketten gleich sind |
<String1> != <String2> | wahr, wenn Zeichenketten verschieden sind |
Algebraische Vergleiche ganzer Zahlen | |
Operator | Bedeutung |
-eq | equal - gleich |
-ne | not equal - ungleich |
-ge | greater than or equal - größer gleich |
-gt | greater than - größer |
-le | less than or equal - kleiner gleich |
-lt | less than - kleiner |
Logische Verknüpfung zweier Argumente | |
UND | <bedingung1> -a <bedingung2> |
ODER | <bedingung1> -o <bedingung2> |
Klammern | \( <ausdruck> \) |
Negation | ! <ausdruck> |
if kommandoliste then kommandos fi
if kommandoliste then kommandos else kommandos fi
if kommandoliste1 then kommandos elif kommandoliste2 then kommandos elif ... ... fi
USERS=`who | wc -l` # Zeilen der who-Ausgabe zählen if test $USERS -gt 5 then echo "Mehr als 5 Benutzer am Geraet" fi
Das geht natürlich auch kürzer und ohne Backtics:
if [ $(who | wc -l) -gt 5 ] ; then echo "Mehr als 5 Benutzer am Geraet" fi
Man sollte bei der Entwicklung von Skripts aber ruhig mit der Langfassung beginnen und sich erst der Kurzfassung zuwenden, wenn man mehr Übung hat und die Langfassungen auf Anhieb funktionieren. Ein weiteres Beispiel zeigt eine Fehlerprüfung:
if test $# -eq 0 then echo "usage: sortiere filename" >&2 else sort +1 -2 $1 | lp fi
Das nächste Beispiel zeigt eine mehr oder weniger intelligente Anzeige für Dateien und Verzeichnisse. 'show' zeigt bei Dateien den Inhalt mit 'less' an und Verzeichnisse werden mit 'ls' präsentiert. Fehlt der Parameter, wird interaktiv nachgefragt:
if [ $# -eq 0 ] # falls keine Angabe then # interaktiv erfragen echo -n "Bitte Namen eingeben: " read DATEI else DATEI=$1 fi if [-f $DATEI ] # wenn normale Datei then # dann ausgeben less $DATEI elif [ -d $DATEI ] # wenn aber Verzeichnis then # dann Dateien zeigen ls -CF $DATEI else # sonst Fehlermeldung echo "cannot show $DATEI" fi
Das nächste Beispiel hängt eine Datei an eine andere Datei an; vorher erfolgt eine Prüfung der Zugriffsberechtigungen: append Datei1 Datei2
if [ -r $1 -a -w $2 ] then cat $1 >> $2 else echo "cannot append" fi
Beim Vergleich von Zeichenketten sollten möglichst die Anführungszeichen (" ... ") verwendet werden, da sonst bei der Ersetzung durch die Shell unvollständige Test-Kommandos entstehen können. Dazu ein Beispiel:
if [ ! -n $1 ] ; then echo "Kein Parameter" fi
Ist $1 wirklich nicht angegeben, wird das Kommando reduziert zu:
if [ ! -n ] ; then ....Es ist also unvollständig und es erfolgt eine Fehlermeldung. Dagegen liefert
if [ ! -n "$1" ] ; then echo "Kein Parameter" fi
bei fehlendem Parameter den korrekten Befehl if [ ! -n "" ]
Bei fehlenden Anführungszeichen werden auch führende Leerzeichen der Variablenwerte oder Parameter eliminiert.
Noch ein Beispiel: Es kommt ab und zu vor, daß eine Userid wechselt oder daß die Gruppenzugehörigkeit von Dateien geändert werden muß. In solchen fällen helfen die beiden folgenden Skripts:
!/bin/sh # Change user-id # if [ $# -ne 2 ] ; then echo "usage `basename $0` <old id> <new id>" exit fi find / -user $1 -exec chown $2 {} ";" #!/bin/sh # Change group-id # if [ $# -ne 2 ] ; then echo "usage `basename $0` <old id> <new id>" exit fi find / -group $1 -exec chgrp $2 {} ";"
case selector in Muster-1) Kommandofolge 1 ;; Muster-2) Kommandofolge 2 ;; .... Muster-n) Kommandofolge n ;; esac
Die Variable selector (String) wird der Reihe nach mit den Mustern "Muster-1" bis "Muster-n" verglichen. Bei Gleichheit wird die nachfolgende Kommandofolge ausgeführt und dann nach der case-Anweisung (also hinter dem esac) fortgefahren.
case selector in Muster1) Kommandofolge1 ;; Muster2 | Muster3) Kommandofolge2 ;; *) Kommandofolge3 ;; esac
Beispiel 1: Automatische Bearbeitung von Quell- und Objekt-Dateien. Der Aufruf erfolgt mit 'compile Datei'.
case $1 in *.s) as $1 ;; # Assembler aufrufen *.c) cc -c $1 ;; # C-Compiler aufrufen *.o) cc $1 -o prog ;; # C-Compiler als Linker *) echo "invalid parameter: $1";; esac
Beispiel 2: Menü mit interaktiver Eingabe:
while : # Endlosschleife (s. später) do tput clear # Schirm löschen und Menütext ausgeben echo " +---------------------------------+" echo " | 0 --> Ende |" echo " | 1 --> Datum und Uhrzeit |" echo " | 2 --> aktuelles Verzeichnis |" echo " | 3 --> Inhaltsverzeichnis |" echo " | 4 --> Mail |" echo "+----------------------------------+" echo "Eingabe: \c" # kein Zeilenvorschub read ANTW case $ANTW in 0) kill -9 0 ;; # und tschuess 1) date ;; 2) pwd ;; 3) ls -CF ;; 4) elm ;; *) echo "Falsche Eingabe!" ;; esac done
for selector in liste do Kommandofolge done
Die Selektor-Variable wird nacheinander durch die Elemente der Liste ersetzt und die Schleife mit der Selektor-Variablen ausgeführt. Beispiele:
for X in hans heinz karl luise # vier Listenelemente do echo $X done Das Programm hat folgende Ausgabe:
hans heinz karl luise
for FILE in *.txt # drucke alle Textdateien do # im aktuellen Verzeichnis lpr $FILE done
for XX in $VAR # geht auch mit do echo $XX done
for selector do Kommandofolge done
Die Selektor-Variable wird nacheinander durch die Parameter $1 bis $n ersetzt und mit diesen Werten die Schleife durchlaufen. Es gibt also $# Schleifendurchläufe. Beispiel:
Die Prozedur 'makebak' erzeugt für die in der Parameterliste angegebenen Dateien eine .bak-Datei.
for FF do cp $FF ${FF}.bak done
while Bedingung do Kommandofolge done
Solange der Bedingungsausdruck den Wert 'true' liefert, wird die Schleife ausgeführt. Beispiele:
Warten auf eine Datei (z. B. vom Hintergrundprozeß)
while [ ! -f foo ] do sleep 10 # Wichtig damit die Prozesslast nicht zu hoch wird done
Pausenfüller für das Terminal
Abbruch mit DEL-Taste
while : do tput clear # BS löschen echo "\n\n\n\n\n" # 5 Leerzeilen banner $(date '+ %T ') # Uhrzeit groß sleep 10 # 10s Pause done
Umbenennen von Dateien durch Anhängen eines Suffix
# Aufruf change suffix datei(en) if [ $# -lt 2 ] ; then echo "Usage: `basename $0` suffix file(s)" else SUFF=$1 # Suffix speichern shift while [ $# -ne 0 ] # solange Parameter da sind do mv $1 ${1}.$SUFF # umbenennen shift done fi
Umbenennen von Dateien durch Anhängen eines Suffix
Variante 2 mit for
# Aufruf change suffix datei(en) if [ $# -lt 2 ] ; then echo "Usage: `basename $0` suffix file(s)" else SUFF=$1 # Suffix speichern shift for FILE do mv $FILE ${FILE}.$SUFF # umbenennen shift done fi
until Bedingung do Kommandofolge done
Die Schleife wird solange abgearbeitet, bis Bedingungsausdruck einen Wert ungleich Null liefert. Beispiele:
# warten auf Datei foo until [ -f foo ] do sleep 10 done
oder Warten auf einen Benutzer:
# warten, bis sich der Benutzer hans eingeloggt hat TT=`who | grep -c "hans"` until [ $TT -gt 0 ] do sleep 10 TT=`who | grep -c "hans"` done
# warten, bis sich der Benutzer hans eingeloggt hat # Variante 2 - kuerzer until [ `who | grep -c "hans"` -gt 0 ] do sleep 10 done
select VAR in Wortliste do Kommandofolge done
Die Select-Kontrollstruktur bietet eine Kombination aus menügesteuerter Verzweigung
und Schleife. Die Wortliste wird als numerierte Liste (Menü) auf dem
Standardfehlerkanal ausgegeben. Mit dem PS3-Prompt wird daraufhin eine Eingabe von der
Tastatur angefordert. Eine leere Eingabe führt zu einer erneuten Anzeige des
Menüs.
Wenn ein Wort aus der Wortliste durch die Eingabe seiner Nummer bestimmt wird,
führt die Shell die Kommandofolge aus und stellt dabei das ausgewählte
Wort in der Variablen VAR und die die Eingabezeile ist aber in der Variablen REPLY
zur Verfügung. Wird in der Eingabezeile keine passende Zahl
übergeben, ist VAR leer.
Menüteil und Ausführung der Liste werden so lange wiederholt, bis die Schleife mit break oder return verlassen wird. Es ist möglich, mit Ctrl-D das Menü unmittelbar zu verlassen. Wenn die Wortliste fehlt (nur die Zeile select VAR), werden stattdessen die Positionsparameter $0 ... $9 verwendet. Beispiel:
export PS3="Ihre Wahl: " select EING in eins zwei drei fertig do echo "EING=\"$EING\" REPLY=\"$REPLY\"" if [ "$EING" = "fertig" ] ; then break fi done
break | Schleife verlassen |
continue | Sprung zum Schleifenanfang |
echo | Ausgabe |
eval | Mehrstufige Ersetzung |
exec | Überlagerung der Shell durch ein Kommando |
exit | Shell beenden |
export | Variablen für Subshells bekannt machen |
read | Einlesen einer Variablen |
shift | Parameterliste verschieben |
trap | Behandlung von Signalen |
Der Aufruf von set ohne Parameter liefert die aktuelle Belegung der Shell-Variablen. Außerdem kann set verwendet werden, um die Positionsparameter zu besetzen.
set eins zwei drei vier besetzt die Parameter mit $1=eins, $2=zwei, $3=drei und $4=vier. Da dabei auch Leerzeichen, Tabs, Zeilenwechsel und anderes "ausgefiltert" wird (genauer alles, was in der Variablen IFS steht), ist set manchmal einfacher zu verwenden, als die Zerlegung einer Zeile mit cut. Die Belegung der Parameter kann auch aus einer Variablen (z. B. set $VAR) oder aus dem Ergebnis eines Kommandoaufrufs erfolgen. Beispiel:
set `date` # $1=Fri $2=Apr $3=28 $4=10:44:16 $5=MEZ $6=1999 echo "Es ist $4 Uhr" Es ist 10:44:16 UhrAber es gibt Fallstricke. Wenn man beispielsweise den Output von "ls" bearbeiten möchte, gibt es zunächst unerklärliche Fehlermeldungen (set: unknown option):
ls -l > foo echo "Dateiname Laenge" while read LINE do set $LINE echo $9 $5 done < foo rm fooDa die Zeile mit dem Dateityp und den Zugriffsrechten beginnt, und für normale Dateien ein "-" am Zeilenbeginn steht, erkennt set eine falsche Option (z. B. "-rwxr-xr-x"). Abhilfe schafft das Voranstelle eines Buchstabens:
ls -l > foo echo "Dateiname Laenge" while read LINE do set Z$LINE echo $9 $5 done < foo rm foo
Weitere Beispiele: Wenn ein Benutzer eingeloggt ist, wird ausgegeben seit wann. Sonst erfolgt eine Fehlermeldung.
if HELP=`who | grep $1` then echo -n "$1 ist seit " set $HELP echo "$5 Uhr eingeloggt." else echo "$1 ist nicht auffindbar" fi
Ersetzen der englischen Tagesbezeichung durch die deutsche:
set `date` case $1 in Tue) tag=Die;; Wed) tag=Mit;; Thu) tag=Don;; Sat) tag=Sam;; Sun) tag=Son;; *) tag=$1;; esac echo $tag $3.$2 $4 $6 $5
Die Bash wurde auch an dieser Stelle erweitert. Mit der doppelten Klammerung $((Ausdruck)) kann man rechnen, ohne ein externes Programm aufzurufen. expr Ausdruck und $((Ausdruck)) beherrschen die vier Grundrechenarten:
+ | Addition |
- | Subtraktion |
* | Multiplikation |
/ | Division |
% | Divisionsrest (Modulo-Operator) |
Die Priorität "Punkt vor Strich" gilt auch hier. Außerdem können Klammern gesetzt werden. Da die Klammern und der Stern auch von der Shell verwendet werden, müssen diese Operationszeichen immer durch den Backslash geschützt werden: '\*', '\(', '\)' . Damit die Operatoren von der Shell erkannt werden, müssen sie von Leerzeichen eingeschlossen werden. Zum Beispiel eine Zuweisung der Summe von A und B an X durch:
X=`expr $A + $B`
oder
X=$((expr $A + $B))
(Backquotes beachten!) Außerdem sind logische Operationen implementiert, die den Wert 0 für 'wahr' und den Wert 1 für 'falsch' liefern.
expr1 | expr2 | oder |
expr1 & expr2 | und |
expr1 < expr2 | kleiner |
expr1 <= expr2 | kleiner oder gleich |
expr1 > expr2 | größer |
expr1 >= expr2 | größer oder gleich |
expr1 = expr2 | gleich |
expr1 != expr2 | ungleich |
Beispiele:
# Mittelwert der positiven Zahlen, die von stdin gelesen werden SUM=0 COUNT=0 while read $WERT # lesen, bis zum ^D do COUNT=`expr $COUNT + 1` SUM=`expr $SUM + $WERT` done AVINT=`expr $SUM / $COUNT` echo "Summe: $SUM Mittelwert: $AVINT"
#Nimm-Spiel, interaktiv ANZ=0 if test $# -ne 1 then echo "Usage: $0 Startzahl" else echo "NIM-Spiel als Shell-Skript" echo "Jeder Spieler nimmt abwechselnd 1, 2 oder 3 Hoelzer" echo "von einem Haufen, dessen Anfangszahl beim Aufruf fest-" echo "gelegt wird. Wer das letzte Holz nimmt, hat verloren." echo ANZ=$1 while [ $ANZ -gt 1 ] # bis nur noch 1 Holz do # da ist wiederholen echo "\nNoch $ANZ Stueck. Du nimmst (1 - 3): \c # Benutzer read N if [ $N -lt 1 -o $N -gt 3 ] ; then # Strafe bei Fehleingabe N=1 fi ANZ=`expr $ANZ - $N` # Benutzer nimmt N weg if [ $ANZ -eq 1 ] ; then # Computer muß letztes Holz nehmen echo "\nGratuliere, Du hast gewonnen" exit # Prozedur verlassen else C=`expr \( $ANZ + 3 \) % 4 # Computerzug berechnen if [ $C -eq 0 ] ; then C=1 # Wenn 0 Verlustposition fi echo "Es bleiben $ANZ Stueck. Ich nehme ${C}.\c" ANZ=`expr $ANZ - $C` # Computerzug abziehen echo " Rest $ANZ" fi done # Dem Benutzer bleibt echo "\nIch habe gewonnen" # das letzte Holz fi
exec /bin/csh
als letzte Zeile in der .profile-Datei durch die C-shell ersetzen (Wenn Sie die C-Shell nur Aufrufen, müssen Sie beide Shells beenden, um sich auszuloggen). Das Kommando entspricht also dem Systemcall exec(). Wird jedoch kein Kommando angegeben, kann die E/A der aktuellen Shell dauerhaft umgeleitet werden. Beispiel:
exec 2>fehler
leitet alle folgenden Fehlerausgaben in die Datei "fehler" um, bis die Umleitung explizit durch
exec 2>-
zurückgenommen wird. Es können bei exec auch andere Dateideskriptoren verwendet werden. Ebenso kann auch die Dateiumleitung einer Eingabedatei erfolgen, z. B.:
exec 3< datei
Danach kann mit read <&3 von dieser Datei gelesen werden, bis die Umleitung mit exec 3<- wieder zurückgenommen wird. Man kann also in Shellskripts durch das Einfügen einer exec-Anweisung die Standardausgabe/-eingabe global für das gesamte Skript umleiten, ohne weitere Änderungen vornehmen zu müssen (eine andere Möglichkeit wäre die oben beschriebene Verwendung von { }).
$ A="Hello world!" $ X='$A' $ echo $X $A $ eval echo $X Hello world!
Der Rückgabestatus von eval ist der Rückgabestatus des ausgeführten Kommandos oder 0, wenn keine Argumente angegeben wurden. Ein weiteres Beispiel:
$ cat /etc/passwd | wc -l 76 $ foo='cat /etc/passwd' $ bar=`| wc -l' $ $foo $bar # Fehler: $bar ist Argument von cmdl cat: | : No such file or directory cat: wc: No such file or directory cat: -l: No such file or directory $ eval $foo $bar 76
In diesem Beispiel wird zunächst ein einfaches Kommando gestartet, das die Anzahl
der Zeilen der Datei /etc/passwd bestimmt. Anschließend werden die beiden Teile des
gesamten Kommandos in die zwei Shell-Variablen foo und bar aufgeteilt. Der erste
Aufrufversuch $foo $bar bringt nicht das gewünschte Ergebnis, sondern
lediglich einige Fehlermeldungen, da in diesem Fall der Wert von bar als Argument für
foo interpretiert wird ('cat' wird mit den Dateien '/etc/passwd', '|', 'wc' und '-l'
aufgerufen). Wird jedoch das Kommando eval auf die Argumente $foo und $bar angewendet,
werden diese zunächst zur Zeichenkette "cat /etc/passwd | wc -l" ersetzt.
Diese Zeichenkette wird dann durch das Kommando eval erneut von der Shell gelesen, die
jetzt das Zeichen "|" in der Kommandozeile als Pipesymbol erkennt und das Kommando
ausführt. Das Kommando eval wird üblicherweise dazu eingesetzt, eine
Zeichenkette als Kommando zu interpretieren, wobei zweifach Ersetzungen in den
Argumenten der Kommandozeile vorgenommen werden.
Eine andere Anwendung ist beispielsweise die Auswahl des letzen Parameters der
Kommandozeile. Mit \$$# erhält man die Parameterangabe (bei fünf Parametern
--> $5). Das erste Dollarzeichen wird von der Shell ignoriert (wegen des '\'),
$# hingegen ausgewertet. Durch eval wird der Ausdruck nochmals ausgewertet, man
erhält so den Wert des letzten Parameters:
eval \$$#
Aber Vorsicht, das klappt nur bei 1 - 9 Parametern, denn z. B. der 12. Parameter führt zu $12 --> ${1}2. Es lassen sich mit eval sogar Pointer realisieren. Falls die Variable PTR den Namen einer anderen Variablen, z. B. XYZ, enthält, kann auf den Wert von XYZ durch eval $PTR zurückgegriffen werden, z. B. durch eval echo \$$PTR.
Ist die Kommandoliste leer, werden die entsprechenden Signale abgeschaltet. Bei einfachen Kommandos reichen oft auch die Anführungszeichen, um die Shell-Ersetzung zu verhindern.
Signale sind eine Möglichkeit, wie verschiedenen Prozesse, also gerade laufende Programme, miteinander kommunizieren können. Ein Prozeß kann einem anderen Prozeß ein Signal senden (der Betriebssystemkern spielt dabei den Postboten). Der Empfängerprozeß reagiert auf das Signal, z. B. dadurch, daß er sich beendet. Der Prozeß kann das Signal auch ignorieren. Das ist beispielsweise nützlich, wenn ein Shellskript nicht durch den Benutzer von der Tastatur aus abgebrochen werden soll. Mit dem trap-Kommando kann man festlegen, mit welchen Kommandos auf ein Signal reagiert werden soll bzw. ob überhaupt reagiert werden soll.
Neben anderen können folgende Signalnummern verwendet werden:
0 | SIGKILL | Terminate (beim Beenden der shell) |
1 | SIGHUP | Hangup (beim Beenden der Verbindung zum Terminal oder Modem) |
2 | SIGINT | Interrupt (wie Ctrl-C-Taste am Terminal) |
3 | SIGQUIT | Abbrechen (Beenden von der Tastatur aus) |
9 | SIGKILL | Kann nicht abgefangen werden - Beendet immer den empfangenden Prozeß |
15 | SIGTERM | Terminate (Software-Terminate, Voreinstellung) |
Die Datei /usr/include/Signal.h enthält eine Liste aller Signale.
Beispiele:
# Skript sperren gegen Benutzerunterbrechung: trap "" 2 3
oder auch
# Skript sauber beenden trap 'rm tmpfile; cp foo fileb; exit' 0 2 3 15
Bitte nicht das exit-Kommando am Schluss vergessen, sonst wird das Skript nicht beendet. Wiedereinschalten der Signale erfolgt durch trap [Signale]. Ein letztes Beispiel zu trap:
# Automatisches Ausführen des Shellskripts .logoff beim # Ausloggen durch den folgenden Eintrag in .profile: trap .logoff 0
xargs Programm [Parameter]
Ein Beispiel soll die Funktionsweise klarmachen:
$ Is *.txt | xargs echo Textdateien:
Hier erzeugt ls eine Liste aller Dateien mit der Endung .txt im aktuellen Verzeichnis. Das Ergebnis wird über die Pipe an xargs weitergereicht. xargs ruft echo mit den Dateinamen von ls als zusätzliche Parameter auf. Der Output ist dann:
Textdateien: kap1.txt kap2.txt kap3.txt h.txt
Durch Optionen ist es möglich, die Art der Umwandlung der Eingabe in Argumente durch xargs zu beeinflussen. Mit der Option -n <Nummer> wird eingestellt, mit wievielen Parametern das angegebene Programm aufgerufen werden soll. Fehlt der Parameter, nimmt xargs die maximal mögliche Zahl von Parametern. Je nach Anzahl der Parameter ruft xargs das angegebene Programm einmal oder mehrmal auf. Dazu ein Beispiel. Vergleich einer Reihe von Dateien nacheinander mit einer vorgegebenen Datei:
ls *.dat | xargs -n1 cmp compare Muster
Die Vergleichsdatei "Muster" wird der Reihe nach mitels cmp mit allen Dateien verglichen,
die auf ".dat" enden. Die Option -n1 veranlaßt xargs, je Aufruf immer nur einen Dateinamen
als zusätzliches Argument bei cmp anzufügen.
Mit der Option - i <Zeichen> ist es möglich, an einer beliebigen Stelle im Programmaufruf,
auch mehrmals, anzugeben, wo die eingelesenen Argumente einzusetzen sind. In diesem Modus
liest xargs jeweils ein Argument aus der Standardeingabe, ersetzt im Programmaufruf jedes
Vorkommen des hinter - i angegebenen Zeichens durch dieses Argument und startet das Programm.
In dem folgenden Beispiel wird das benutzt, um alle Dateien mit der Endung ".txt" in
".bak" umzubenennen.
ls *.txt | cut -d. f1 | xargs -iP mv P.txt P.bak
Das Ganze funktioniert allerdings nur, wenn die Dateien nicht noch weitere Punkte im Dateinamen haben.
xargs ist überall dann besonders notwendig, wenn die Zahl der Argumente recht groß werden kann. Obwohl Linux elend lange Kommandozeilen zuläßt, ist die Länge doch begrenzt. xargs nimmt immer soviele Daten aus dem Eingabestrom, wie in eine Kommandozeile passen und führt dann das gewünschte Kommando mit diesen Parametern aus. Liegen weitere Daten vor, wird das Kommando entsprechend oft aufgerufen. Insbesondere mit dem folgenden Kommando sollte xargs verwendet werden, da find immer den gesamten Dateipfad liefert, also schnell recht lange Argumente weitergibt.
dialog bietet im einzelnen folgende Interaktionsmöglichkeiten:
Aufruf:
dialog --title "Fenstertitel" \ --backtitle "Hintergrundtitel" \ --[infobox | yesno | menu | ...] \ Fensterinhalt u. -abmaße
aufgerufen. Nach Beendigung von dialog durch den Benuter (Abbruch über Escape-Taste, "OK" / "Yes" bzw. "Cancel" / "No") liefert dialog folgende Informationen zurück:
dialog --backtitle "$BTITLE" --title "Auswahl-Menu" \ --menu "Bitte treffen Sie Ihre Auswahl:" 12 45 3 \ "Pizza Regina" "heute besonders köstlich zubereitet" \ "vino chianti" "beschränken wir uns auf das wesentliche" \ "grappa" "reduced to the max"\ 2 > dialog-dat.tmp
Bei Beendigung von dialog über "OK" wird der ausgewählte Menu-Punkt über den Standard-Fehlerkanal ausgegeben. Da der Standard-Fehlerkanal in die Datei dialog-dat.tmp umgelenkt ist, wird der ausgewählte Punkt demnach in diese Datei geschrieben und kann von dort z. B. mit read AUSWAHL < dialog-dat.tmp gelesen werden.
Beim Einbinden von dialog-Abfragen in Skripte müssen zwei Dinge besonders beachtet werden:
( cat file1 ; echo "$SHELLVAR" ) > file2
if [ $# -eq 0 ] then echo "Usage: `basename $0` Name [Name ..]" exit 2 fi for SUCH in $* do if [ ! -z $SUCH ] ; then grep $SUCH << "EOT" Hans 123456 Fritz 234561 Karl 345612 Egon 456123 EOT fi done
if test $# -ne 1 then echo "Usage: dir Pfad" exit 2 fi cd $1 echo pwd ls -CF for I in * do if test -d $I then dir $I fi done
# find liefert die vollständigen Pfadnamen (temporäre Datei). # Mit ed werden die "/"-Zeichen erst zu "|---- " expandiert # und dann in den vorderen Spalten aus "|---- " ein Leerfeld # " |" gemacht. if test $# -lt 1 then echo "Usage: tree Pfadname [Pfadname ...] [find-Options]" else TMPDAT=$0$$ find $@ -print > $TMPDAT 2>/dev/null ed $TMPDAT << "EOT" >/dev/null 2>/dev/null 1,$s/[^\/]*\//|---- /g 1,$s/---- |/ |/g w q EOT cat $TMPDAT rm $TMPDAT fi
Mit dem Stream-Editor sed kann das sogar noch kompakter formuliert werden:
if [ $# -lt 1 ] then echo "Usage: tree Pfadname [Pfadname ...] [find-Options]" else find $@ -print 2>/dev/null | \ sed -e '1,$s/[^\/]*\//|---- /g' -e '1,$s/---- |/ |/g' fi
# pick - Argumente mit Abfrage liefern for I ; do echo "$I (j/n)? \c" > /dev/tty read ANTWORT case $ANTWORT in j*|J*) echo $I ;; q*|Q*) break ;; esac done </dev/tty
echo "Bitte Passwort eingeben: \c" stty -echo # kein Echo der Zeichen auf dem Schirm read CODE stty echo tput clear # BS loeschen trap "" 2 3 banner " Terminal " banner " gesperrt " MATCH="" DELAY=1 while [ "$MATCH" != "$CODE" ] do sleep $DELAY echo "Bitte Passwort eingeben: \c" read MATCH DELAY='expr $DELAY \* 2` # doppelt so lange wie vorher done echo
#!/bin/sh # Suchen im Pfad nach einer Kommando-Datei OPATH=$PATH PATH=/bin:/usr/bin if [ $# -eq 0 ] ; then echo "Usage: which kommando" ; exit 1 fi for FILE do for I in `echo $OPATH | sed -e 's/^:/.:/' -e 's/::/:.:/g \ -e 's/:$/:./'` do if [ -f "$I/$FILE" ] ; then ls -ld "$I/$FILE" fi done done
echo "Zahl eingeben: \c"; read ZAHL P=2 while test `expr $P \* $P` -le $ZAHL; do while test `expr $ZAHL % $P` -ne 0; do if test $P -eq 2; then P=3 else P=`expr $P + 2` fi done ZAHL=`expr $ZAHL / $P` echo $P done if test $ZAHL -gt 1; then echo $ZAHL fi echo ""
if [ $# -eq 0 ] ; then echo "Osterdatum fuer Jahr: \c"; read JAHR else JAHR="$1" fi G=`expr $JAHR % 19 + 1` C=`expr $JAHR / 100 + 1` X=`expr \( $C / 4 - 4 \) \* 3` Z=`expr \( $C \* 8 + 5 \) / 25 - 5` D=`expr $JAHR \* 5 / 4 - $X - 10` E=`expr \( $G \* 11 + $Z - $X + 20 \) % 30` if test $E -lt 0; then $E=`expr $E + 30` fi if [ $E -eq 25 -a $G -gt 11 -o $E -eq 24 ] ; then E=`expr $E + 1` fi TAG=`expr 44 - $E` if [ $TAG -lt 21 ] ; then TAG=`expr $TAG + 30` fi TAG=`expr $TAG + 7 - \( $D + $TAG \) % 7` if [ $TAG -gt 31 ] ; then TAG=`expr $TAG - 31` MON=4 else MON=3 fi echo "Ostern $JAHR ist am ${TAG}.${MON}.\n"
Statt des expr-Befehls kann bei der Bash auch das Konstrukt $(( ... )) verwendet werden. Das Programm sieht dann so aus:
if [ $# -eq 0 ] ; then echo "Osterdatum fuer Jahr: \c"; read JAHR else JAHR="$1" fi G=$(($JAHR % 19 + 1)) C=$(($JAHR / 100 + 1)) X=$((\( $C / 4 - 4 \) \* 3)) Z=$((\( $C \* 8 + 5 \) / 25 - 5)) D=$(($JAHR \* 5 / 4 - $X - 10)) E=$((\( $G \* 11 + $Z - $X + 20 \) % 30)) if test $E -lt 0; then $E=$(($E + 30)) fi if [ $E -eq 25 -a $G -gt 11 -o $E -eq 24 ] ; then E=$(($E + 1)) fi TAG=$((44 - $E)) if [ $TAG -lt 21 ] ; then TAG=$(($TAG + 30)) fi TAG=$(($TAG + 7 - \( $D + $TAG \) % 7)) if [ $TAG -gt 31 ] ; then TAG=$(($TAG - 31)) MON=4 else MON=3 fi echo "Ostern $JAHR ist am ${TAG}.${MON}.\n"
0,15,30,45 * * * * /home/sbin/turmuhr
So wird das Skript turmuhr alle Viertelstunden aufgerufen. Es werden zwei Sounddateien verwendet, hour.au für den Stundenschlag und quater.au für den Viertelstundenschlag. Statt des Eigenbau-Programms audioplay kann auch der sox verwendet werden oder man kopiert die Dateien einfach nach /dev/audio. Die Variable VOL steuert die Lautstärke.
#!/bin/sh BELL=/home/local/sounds/hour.au BELL1=/home/local/sounds/quater.au PLAY=/usr/bin/audioplay VOL=60 DATE=`date +%H:%M` MINUTE=`echo $DATE | sed -e 's/.*://'` HOUR=`echo $DATE | sed -e 's/:.*//'` if [ $MINUTE = 00 ] then COUNT=`expr \( $HOUR % 12 + 11 \) % 12` BELLS=$BELL while [ $COUNT != 0 ]; do BELLS="$BELLS $BELL" COUNT=`expr $COUNT - 1` done $PLAY -v $VOL -i $BELLS elif [ $MINUTE = 15 ] then $PLAY -v $VOL -i $BELL1 elif [ $MINUTE = 30 ] then $PLAY -v $VOL -i $BELL1 $BELL1 elif [ $MINUTE = 45 ] then $PLAY -v $VOL -i $BELL1 $BELL1 $BELL1 else $PLAY -v $VOL -i $BELL1 fi
Funktionsname () { Kommandofolge }
Steht die schließende geschweifte Klammer nicht in einer eigenen Zeile, gehört ein Strichpunkt davor. Die runden Klammern hinter dem Funktionsnamen teilen dem Kommandozeileninterpreter der Shell mit, daß nun eine Funktion definiert werden soll (und nicht ein Kommando Funktionsname aufgerufen wird). Es kann keine Parameterliste in den Klammern definiert werden.
Der Aufruf der Shellfunktion erfolgt durch Angabe des Funktionsnamens, gefolgt von Parametern (genauso wie der Aufruf eines Skripts). Die Parameter werden innerhalb der Funktion genauso, wie beim Aufruf von Shellskripts über $1 bis $nn angesprochen. Ein Wert kann mit der Anweisung 'return <Wert>' zurückgegeben werden, er ist über den Parameter $? abfragbar. Beispiel:
isdir () # testet, ob $1 ein Verzeichnis ist { if [ -d $1 ] ; then echo "$1 ist ein Verzeichnis" # Kontrolle zum Test return 0 else return 1 fi }
Im Gegensatz zum Aufruf von Shell-Skripts werden Funktionen in der aktuellen Shell
ausgeführt und sie können bei der Boune-Shell nicht exportiert werden; die Bash
erlaubt dagegen das Exportieren mit export -f. Das folgende Beispiel
illustriert die Eigenschaften von Shellfunktionen.
Die folgende Funktion gibt den Eingangsparameter in römischen Zahlen aus. Dabei
wird die Zahl Schritt für Schritt in der Variablen ZAHL zusammengesetzt.
Würde man der Funktion ZIFF ein Skript verwenden, ginge das nicht, da
sich der Wert von ZAHL ja nicht aus dem aufgerufenen Skript heraustransportieren
ließe.
# # Ausgabe des Eingangsparameters $1 in roemischen Ziffern # ZIFF () # Funktion zur Bearbeitung einer einstelligen Ziffer $1 # Einer-, Zehner-, Hunderterstelle unterscheiden sich nur # durch die verw. Zeichen $2: Einer, $3: Fuenfer, $4: Zehner { X=$1 if test $X -eq 9; then ZAHL=${ZAHL}$2$4 elif test $X -gt 4; then ZAHL=${ZAHL}$3 while test $X -ge 6; do ZAHL=${ZAHL}$2 ; X=`expr $X - 1` done elif test $X -eq 4; then ZAHL=${ZAHL}$2$3 else while test $X -gt 0; do ZAHL=${ZAHL}$2 ; X=`expr $X - 1` done fi } if test $# -eq 0; then echo "Usage: roem Zahl"; exit fi XX=$1 while test $XX -gt 999; do ZAHL=${ZAHL}"M"; XX=`expr $XX - 1000` done ZIFF `expr $XX / 100` C D M XX=`expr $XX % 100` ZIFF `expr $XX / 10` X L C ZIFF `expr $XX % 10` I V X echo "$ZAHL \n"
echo "Alles Loeschen (j/n)\c" stty raw -echo INPUT=`dd count=1 bs=1 2> /dev/null` stty -raw echo echo $INPUT case $INPUT in j|J) echo "Jawoll" ;; n|N) echo "Doch nicht" ;; *) echo "Wat nu?" ;; esac
# Neuen Benutzer eintragen, Home-Directory erzeugen, # .profile kopieren PWDF=/etc/passwd GRPF=/etc/group STDPROF=.profile if test $# -ne 3 ; then echo "Usage: newuser Login-Name Gruppe Voller Name" ; exit 1 fi NAME=$1 ; GRUPPE=$2 HOME=/home/$1 case $NAME in ?????????*) echo "Name ist zu lang" ; exit 1 ;; *[A-Z]*) echo "Name enthaelt Grossbuchstaben" ; exit 1 ;; esac if grep "^$NAME:" $PWDF ; then echo "Name schon vorhanden" ; exit 1 fi UID=`tail -1 $PWDF | cut -f3 -d:` UID=`expr $UID + 1` if grep ".*:.*:$UID:" $PWDF ; then echo "Passwortdatei ist nicht sortiert" : exit 1 fi if grep ".*:.*:$GRUPPE:" $GRPF ; then : else echo "Gruppen-Nummer nicht vorhanden" ; exit 1 fi mkdir $HOME cp $STDPROF $HOME chown $UID $HOME $HOME/$STDPROF chgrp $GRUPPE $HOME $HOME/$STDPROF echo $NAME::$UID:$GRUPPE:$3:$HOME:/bin/sh >>$PWDF echo $NAME::$UID:$GRUPPE:$3:$HOME:/bin/sh
Mit useradd geht das ganze viel einfacher, aber hier geht es ja um ein Beispiel. Das folgende Skript vereinfacht die Anwendung von useradd, das relativ viele Parameter besitzt.
#!/bin/sh # Shell-Script zum Anlegen eines Benutzers # Aufruf: newuser username gruppe Bemerkung # if [ $# != 3 ]; then echo "Usage: `basename $0` username gruppe Bemerkung" exit 2 fi GRUP=$2 USR=$1 BEM=$3 GRP=/etc/group PASS=/etc/passwd SHAD=/etc/shadow SKEL=/etc/skel cp $PASS ${PASS}.bak cp $SHAD ${SHAD}.bak if [ "$USR" != "" ] ; then echo "--- Anlegen User: $USR, Bemerkung: $BEM ---" if `grep -s $GRUP $GRP >/dev/null` ; then : else echo "--- Gruppe $GRUP unbekannt ---" exit 2 fi if `grep -s $USR $PASS` ; then echo "+++ User existiert bereits +++" exit 2 fi /usr/sbin/useradd -d /home/${USR} -g $GRUP -s /bin/sh -c "$BEM" -m -k $SKEL $USR if [ $? -eq 0 ] ; then echo "+++ $USR angelegt +++" else echo "--- Fehler beim Anlegen von $USR ---" fi while [ -f /etc/ptmp ]; do sleep 1 done fi echo "--- Fertig ---"
# rm-all - Löschen User nach Stichwort in der Passwort-Datei if [ $# -ne 1 ]; then echo "Aufruf: $0 Suchbegriff" exit 1 fi # Erst mal testweise die Kandidaten fürs Löschen ausgeben KANDIDATEN=`grep "$1" /etc/passwd | sed -e '1,$s/:.*//'` echo "$KANDIDATEN loeschen (j/n)? \c" read ANTWORT if [$ANTWORT != "j" ]; then echo "Abgebrochen!" exit 1 fi # jetzt wird wirklich gelöscht /bin/rmuser $KANDIDATEN
Das Skript "rmuser" ist etwas aufwendiger, da einige Sicherheitstest notwendig sind. Auf den meisten Systemen gibt es ein Programm "userdel", das den Benutzer aus /etc/passwd, /etc/group und /etc/shadow löscht. Bei anderen Anlagen könnte man das Gleiche mit einem Skript erledigen. Das Löschprotokoll wird per Mail an root geschickt.
# rmuser - löschen user if [ $# -lt 1 ]; then echo "Aufruf: $0 user [user ....]" exit 1 fi { # Sicherheitskopien anlegen cp /etc/passwd /etc/passwd.bak cp /etc/shadow /etc/shadow.bak # Jetzt wird es ernst while [ $# -gt 0 ] ; do USR=$1 N=`grep -c "$USR" /etc/passwd` if [ $N -ne 1 ]; then echo "$USR nicht vorhanden oder doppelt" else if [ `grep $USR /etc/passwd | cut -d: -f3` -lt 100 ]; then echo "$USR hat eine ID kleiner 100, nicht geloescht" else echo "*** Loeschen User: $USER" # Homedir aus /etc/passwd extrahieren HOM=`grep $USR /etc/passwd | cut -f6 -d:` rm -rf $HOM 2>&1 find / -user $USR -exec rm -f {} ";" 2>&1 /usr/sbin/userdel $USR echo "--- $USR erledigt ..." echo "" fi fi shift done } | mailx -s "User-Loeschung" root
Man könnte das Skript noch um eine Prüfung auf weitere nicht zu löschende User ergänzen. Außerdem sollte man das Ganze erst starten, wenn sonst kein User mehr im System ist. Da das Skript bei vielen User recht lange dauert, ist es am günstigsten, es als Batch im Hintergrund zu starten.
#!/bin/sh # PATH=/bin:/usr/bin NEW=/tmp/WW1.WHO OLD=/tmp/WW2.WHO >$OLD # OLD neu anlegen while : # Endlosschleife do who >$NEW diff $OLD $NEW mv $NEW $OLD sleep 60 done
#!/bin/sh # Calculate the amount of space used by the specified files # Default is the actual directory SUM=0 TMPF=$HOME/$0$$ ls -l $* >$TMPF while read D1 D2 D3 D4 D5 REST ; do # lesen aus TMPF # Feld 5 enthaelt Groesse SUM=`expr $SUM + 0$D5 / 1024` done < $TMPF echo "$SUM KBytes" rm $TMPF
Preisfrage: Warum funktioniert folgende Variante nicht?
#!/bin/sh SUM=0 ls -l | while read D1 D2 D3 D4 D5 REST ; do SUM=`expr $SUM + 0$D5 / 1024` done echo "$SUM KBytes"
otest -a -p "Parameter" -c *.txt
#!/bin/sh # Bearbeiten von Optionen in Shellskripts # Beispiel: -a -b -c als einfache Optionen # -p <irgend ein Parameter> als "Spezial-Option" READOPT=0 while [ $READOPT -eq 0 ] ; do # solange Optionen vorhanden case $1 in -a) echo "Option a" shift ;; -b) echo "Option b" shift ;; -c) echo "Option c" shift ;; -p) PARAM=$2 ; shift # Parameter lesen echo "Option p: $PARAM" shift ;; *) if `echo $1 | grep -s '^-'` ; then # Parm. beginnt mit '-' echo "unknown option $1" shift else READOPT=1 # Ende Optionen, kein shift! esac done echo "Restliche Parameter : $*"
#!/bin/sh # Alle Dateien umbennen, die durch $3 - $n spezifiziert werden # dabei wird der String $1 im Dateinamen durch $2 ersetzt, # wobei auch regulaere Ausdruecke erlaubt sind if [ $# -lt 3 ] ; then echo 'Usage: ren <old string> <new string> files' echo 'Example: ren foo bar *.foo renames all files' echo ' *.foo ---> *.bar' exit 1 fi S1=$1 ; shift S2=$1 ; shift while [ $# -gt 0 ]; do for OLDF in $1 ; do NEWF=`echo $OLDF | sed -e "s/${S1}/${S2}/"` if [ -f $NEWF ] ; then echo "$NEWF exists, $OLDF not renamed" else echo "renaming $OLDF to $NEWF" mv $OLDF $NEWF fi done shift done
#!/bin/sh # Loeschen Prozess durch Angabe eines Musters TMPF=$HOME/zap..$$ if [ $# -lt 2 ]; then echo "Usage: zap -Signal Muster"; exit 1 fi SIG=$1 # alle Prozesse nach Stichwort durchsuchen ps auwx | grep $2 | sed -e '1,$s/ */ /g' > $TMPF while read X do set $X echo "$X (j/n)? \c" read ANTWORT </dev/tty case $ANTWORT in j*|J*) X=`ps auwx | grep -c $2` if [ $X -ne 0 ]; then kill $SIG $2 fi ;; q*|Q*) break ;; esac done <$TMPF rm $TMPF
#!/bin/sh # Taeglicher Backup, als Parameter wird ein # Verzeichnis angegeben if [ $# -eq 0 ] ; then echo "Aufruf: $0 [-a] <directory>" echo "-a Alles sichern (sonst incrementell)" exit fi echo "\nBand einlegen und [RETURN] druecken!" read DUMMY if [ "$1" = "-a" -o "$1" = "-A" ] ; then if [ -d "$2" ] ; then echo "Komplett-Backup von $2 ..." MARKER=$2/.lastbackup find $2 -depth -print | cpio -ovc >/dev/tape touch $MARKER echo "Fertig!" else echo "$2 ist kein Verzeichnis!" exit fi else if [ -d "$1" ] ; then echo "Inkrementeller Backup von $1 ..." MARKER=$1/.lastbackup find $1 -newer $MARKER -print | cpio -ovc >/dev/tape touch $MARKER echo "Fertig!" else echo "$2 ist kein Verzeichnis!" exit fi fi echo "\nBand herausnehmen\n"
#!/bin/sh # # Inkrementelles Sichern aller Dateien des WWW-Servers # { TMPFILE="/tmp/check.$$" TIMESTAMP="`date +%y%m%d`" DIRECTORY="/home/httpd/htdocs" WWWARCHIVE="/home/wwwarchive" cd $DIRECTORY find . -newer .lastcheck -print >$TMPFILE 2>/dev/null touch .lastcheck if [ `cat $TMPFILE | wc -l` -gt 0 ] then tar cf /$WWWARCHIVE/backup.$TIMESTAMP.tar $DIRECTORY gzip /$WWWARCHIVE/backup.$TIMESTAMP.tar chown wwwadm.staff /$WWWARCHIVE/backup.$TIMESTAMP.tar.gz chmod 660 /$WWWARCHIVE/backup.$TIMESTAMP.tar.gz fi rm $TMPFILE } > /dev/null 2>&1
while true ; do echo "`pwd`:$PS1\c" read KDO eval $KDO done
#!/bin/sh # Nachricht ($2-$nn) an User ($1) senden, sofern dieser eingeloggt ist NAM="$1" shift MSG="$@" if who | grep -q $NAM ; then # User eingeloggt? write $NAM < $MSG fi
who | while read USR REST ; do # für alle aktiven User banner"Teatime!" | write $USR done
sort file1 >/tmp/file1.sor sort file2 >/tmp/file2.sor comm /tmp/file1.sor /tmp/file2.sor tmp/file[12].sor
Das folgende Skript namens "!" führt ein Kommando aus, das als Parameter übergeben wird, und liefert den Namen einer temporären Datei zurück, in dem das Ergebnis der Kommandoausführung gespeichert wurde. Das wäre aber noch kein Fortschritt. Der Trick ist, daß die temporäre Datei nach fünf Minuten automatisch gelöscht wird. Unsere Aufgabe läßt sich dann als Einzeiler schreiben:
comm `! sort file1` `! sort file2`
Die Schwierigkeit beim Schreiben von "!" liegt darin, daß die aufrufende Shell ("comm"-Kommando) normalerweise wartet, bis das aufgerufene Skript ("! sort ...") terminiert - aber dieses soll ja fünf Minuten warten und dann die Datei löschen. In diesem Fall wäre aber die temporäre Datei schon wieder leer. Die Lösung zeigt die Auflistung von "!":
#!/bin/sh # Kommado ausführen, # Ergebnis in temp. Datei, # Dateiname zurückgeben TEMPDIR=/tmp TEMPF=$TEMPDIR/BANG..$$ # Trap zum Löschen von TEMPF trap 'rm -f $TEMPF; exit' 1 2 15 # Falls kein Kommando, nur Dateinamen liefern if [ $# -eq 0 ] ; then echo "Usage: `basename $0` command [args]" 1>&2 echo $TEMPF exit 1 fi # Kommando ausführen, Dateiname liefern "$@" > $TEMPF echo $TEMPF # jetzt kommt der Trick: exec >&- # Standardausgabe schließen, rufende Shell wartet nicht! ( sleep 300 ; rm -f $TEMPF ) & # Löschauftrag --> Hintergrund exit 0
#!/bin/sh # Rekursives Skript zum Suchen und Ersetzen von Text-Pattern # PROGNAME=`basename $0` TEMPDAT=/tmp/`basename $0.$$` if test $# -lt 4; then echo "$PROGNAME : Recursive search-and-replace-skript." echo "usage : $PROGNAME <start-dir> <file-expression> \ <search-pattern> <replace-pattern>" echo "example : $PROGNAME . \"*.html\" \"abc\" \"xxx\" " echo "Both patterns use ex/vi-syntax !" else find $1 -type f -name "$2" -print > $TEMPDAT for NAME in `cat $TEMPDAT` do echo -n "Processing $NAME.." ex $NAME << EOT > /dev/null 1,\$ s/$3/$4/g wq EOT echo "done." done rm $TEMPDAT fi
#!/bin/sh # Generieren ls-Rl und ls-RL.Z # Eingabeparameter: Ausgangsverzeichnis # if [ $# -ne 1 ]; then echo "Aufruf: `basename $0` Verzeichnis" exit 1 fi ROOT=$1 LSFILE=$ROOT/ls-lR TMPFILE=/tmp/mkls.$$ cd $ROOT echo "$ROOT" > $TMPFILE # Verzeichnisinfo erstellen, Leerzeilen und Summenangabe raus ls -lR 2>/dev/null | grep -v "^total" | grep -v "^$" >> $TMPFILE cp $TMPFILE $LSFILE cat $LSFILE | compress -f > $TMPFILE cp $TMPFILE ${LSFILE}.Z rm $TMPFILE
#!/bin/sh # # prune: Shorten textfiles listetd in $FLIST. # files are shortened to a certain number of lines at # their end. In $FLIST are lines containing filename # (full path) and number of remaining lines. E. g.: # /var/adm/messages 500 # /var/adm/debug 100 # # /var/adm/wtmp will also be shortened # a.out, core, *.o and tmp-files will be deleted after 8 days # HOSTNAME=/bin/hostname # Pfad hostname-Programm FLIST=/etc/prune_list # Liste zu loeschender Dateien TMPF=/tmp/prune.$$ # Temporaerdatei { while read FILE LEN do if [ -n $FILE -a -n $LEN ] ; then if [ `wc -l $FILE` -lt $LEN ] ; then echo "prune: ${FILE} nothing to do" else tail -$LEN $FILE >$TMPF cp $TMPF $FILE echo "prune: ${FILE} shortened to $LEN lines" fi else echo "prune: error in $FLIST, FILE or LEN missing" fi done < $FLIST rm $TMPF cd /var/adm [ -f wtmp.3 ] && cp wtmp.3 wtmp.4 [ -f wtmp.2 ] && cp wtmp.2 wtmp.3 [ -f wtmp.1 ] && cp wtmp.1 wtmp.2 cp wtmp wtmp.1 cp /dev/null wtmp chmod 664 wtmp echo "wtmp shortened" [ -f wtmpx.3 ] && cp wtmpx.3 wtmpx.4 [ -f wtmpx.2 ] && cp wtmpx.2 wtmpx.3 [ -f wtmpx.1 ] && cp wtmpx.1 wtmpx.2 cp wtmpx wtmpx.1 cp /dev/null wtmpx chmod 664 wtmpx echo "wtmpx shortened" # clean up /tmp and /usr/tmp /usr/bin/find /tmp -type f -atime +7 -exec /bin/rm -f {} \; /usr/bin/find /var/tmp -type f -atime +7 -exec /bin/rm -f {} \; /usr/bin/find /usr/tmp -type f -atime +7 -exec /bin/rm -f {} \; /usr/bin/find / \( -name a.out -name core -name '*.o' \) -atime +7 \ -exec /bin/rm -f {} \; } | mailx -s "Output from PRUNE `$HOSTNAME`" root 2>&1
#!/bin/sh # Programm to run weekly to check some important items # must be run by root # # find accounts without password echo "" echo "Accounts without password" echo "-------------------------" /usr/bin/grep '^[^:]*::' /etc/passwd # find accounts with UID 0 and/or GID 0 echo "" echo "Accounts with ID 0" echo "------------------" /usr/bin/grep ':00*:' /etc/passwd # Check Permissions echo "" echo "Permissions of important files" echo "------------------------------" ls -l /etc/passwd /etc/group /etc/hosts /etc/host.equiv /etc/inetd.conf echo "" echo "SUID-files" echo "-----------" /usr/bin/find / -perm -4000 -type f -exec ls -l {} \; echo "" echo "SGID-files" echo "----------" /usr/bin/find / -perm -2000 -type f -exec ls -l {} \; echo "" echo "World-writable files" echo "--------------------" /usr/bin/find / -perm -2 \( -type f -o -type d \) -exec ls -l {} \; echo "" echo "Files without owner" echo "-------------------" /usr/bin/find / -nouser -exec ls -l {} \; echo "" echo "/var/adm/sulog:" echo "---------------" cat /var/adm/sulog } 2>&1 | mailx -s "Check-Output" root 2>&1
#!/bin/sh # # Erzeugt im WWW-Verzeichnis der Skripten # jeweils gepackte Versionen. # Die Dateien heissen <Verzeichnisname>.tar.gz # cd /home/httpd/lbs/skripten [ `find . -newer ".lastpack" -print | wc -l` -eq 0 ] && exit { ls > ptmp.$$ while read DIR do if [ -d $DIR ] ; then NAME=`basename $DIR` cd $NAME if [ -f .lastupd ] ; then if [ `find . -newer ".lastupd" -print | wc -l` -gt 0 ] ; then rm $NAME.tgz /root/bin/upd-index-html tar cf $NAME.tar * gzip -f $NAME.tar mv $NAME.tar.gz $NAME.tgz touch .lastupd echo "$NAME ... DONE!" else echo "$NAME ... nothing to do" fi else touch .lastupd fi cd .. fi done < ptmp.$$ rm ptmp.$$ touch /home/httpd/lbs/skripten/.lastpack } | mailx -s "Skripten-Packer" webmaster
![]() |
![]() |