update 05.01.07
Zurück zur Homepage von R.Lütticken |
|
Das Skript cl in Beipiel 1 löscht (Befehl
rm - für
remove) alle Dateien mit den angegebenen Endungen. Ruft man es auf, so
wird eine neue Shell gestartet, und innerhalb dieser Shell werden die
Zeilen
des Skripts abgearbeitet. In diesem Fall spricht man von einer
nichtinteraktiven
Shell.
Es gibt unter Unix/Linux verschiedene Shellausführungen. Einerseits wurde die historisch älteste, die Bouneshell, immer wieder weiterentwickelt, andererseits begann mit der C-Shell ein zweiter Strang von Shells, deren Skriptsprache sich stärker an die Programmiersprache C anlehnt und deshalb zu den Nachkommen der Bourneshell nicht kompatibel ist. Nach Boes/Reimann wird die C-Shell auf BSD-Systemen häufig eingesetzt (S355), unter Linux ist die zur Bourne-Shell kompatible Bash üblich, um die es hier ausschließlich geht..(Anmerkung 1) |
Beispiel 2
|
Man erstellt das Skript in einem beliebigen Texteditor, und
speichert
es etwa unter dem Namen bsp2. Es läßt sich nun aber
noch
nicht aufrufen. Die Datei bsp2 muss erst zu einer
ausführbaren
Datei gemacht werden: Durch
|
|
chmod
u+x bsp2
dir bsp2 -rwxr--r-- 1 re users 25 Apr 8 19:10 bsp2 |
Die Ausgabe von dir bsp2 zeigt den Erfolg:: -rwx ..., bedeutet, dass die Datei vom Besitzer gelesen, geschrieben und ausgeführt werden darf. |
|
re@reneu:~ > ls /etc >tempo
re@reneu:~ > wc -w tempo 146re@reneu:~ > rm tempo |
ls legt die Dateinamen in der Datei tempo ab, wc zählt die Worte in tempo. Die Datei tempo ist dann nicht mehr von Nöten und wird gelöscht. Der Umweg über die Festplatte, kann durch eine Pipe umgangen werden: Diese hängt den Eingabekanal von wc an die Ausgabe von ls: |
re@reneu:~ ls /etc | wc -w | Das Zeichen | verbindet beide Kommandos zu einer Pipe, die Ausgabe von ls wird unmittelbar an wc weitergegeben.. |
re@reneu:~ > name=peter
re@reneu:~ > echo "$name ist lieb"peter ist lieb re@reneu:~ > echo '$name ist lieb'$name ist lieb |
In der Zeile name=peter ist
peter
eine Stringkonstante. Regelungen sind notwendig, wenn die Konstante
Sonderzeichen
enthalten soll,- z.B. Blanks. Soll
hallo peter eine Konstante sein, dann müssen die zwei Wörter zusammengebunden werden. Das kann durch Anführungszeichen »hallo peter« oder durch Hochkommas 'hallo peter' geschehen. Beides bewirkt, dass Blanks nicht als Trennungszeichen sondern als normale Buchstaben aufgefasst werden. |
Konkatenation durch einfaches Aneinanderhängen. (In Pascal durch + ):
neu=$name$blank{ist}$blank$was | Durch die geschweifte Klammer kann man erreichen, dass ist nicht mehr zum Variablennamen blank gehört. |
re@reneu:~ >
name=peter
re@reneu:~ > was=doof re@reneu:~ > blank=" " re@reneu:~ > neu=$name${blank}ist$blank$was re@reneu:~ > echo $neu peter ist doof |
Den gleichen Erfolg brachte dieser Versuch |
echo ${#neu} 14 | Länge des Strings: Durch {#Variablenname) |
name="Der Pfad ist /home/daten/text.doc" pfad=/${name#*/} echo $pfad /home/daten/text.docread verzeichnis=${pfad%/*} echo $verzeichnis /home/daten dateiname=${pfad##*/} echo $dateiname text.doc echo ${dateiname:1:3} ext |
Bei name#muster wird alles vom Beginn des Strings ab bis zum
Muster abgehängt. Hier enthält das Muster eine Wildcard * und
den slash. Dadurch wird der erste Slash mit abgehängt. Wird das # durch ein % ersetzt, so wird der String vom Ende ab nach Muster durchsucht, und alles inklusive dieses Musters aus dem String entfernt. Doppelkreuz ## oder %% bedeutet, dass dies bis zum letzten gefundenen Auftauchen von Muster geschieht.So wird für Dateiname alles bis zum letzten Auftauchen von / gelöscht. name:1:3 bedeutet, ab dem Buchstaben mit Nummer 1 (die Zählung beginnt bei 0) werden 3 Zeichen ausgegeben. |
Diese sollten deklariert werden, siehe dazu aber Anmerkung 6 und Skript in Kapitel 4! | re@reneu:~ > declare -i zahl
re@reneu:~
> zahl=3*9
re@reneu:~ > echo $zahl 27 re@reneu:~ > zahl=zahl+1 re@reneu:~ > echo $zahl 28 |
declare -a feld read -a feld peter paul mary echo ${feld[1]} paul |
Feld declarieren. Man muss nichts über die Größe angeben! Nach read feld kann man Werte tippen. Hier werden 3 Werte getippt, die auf den Plätze 0 bis 2 des Arrays gespeichert werden. Anprache der Plätze wie in Pascal über eckige Klammern. Zuweilen, z.B. bei echo feld[1], wird die eckige Klammer nicht als Feldindex gedeutet, da hilft eine geschweifte Klammer wie im Beispiel. Bei der Eingabe sind die Daten hier durch Blnks getrennt gewesen. Was read als Trennung akzeptiert, steht in der Variablen IFS. Laut Dölle soll dort blnk, Tab und Zeilenwechsel stehen. In meinem Sytem ist es Hex 0A,- was ist das? Wie weist man der Variablen IFS Zeilenwechsel zu? Hier wird beschrieben, wie der Inhalt einer Datei in eine Arrayvariable gelangen kann. |
Exportieren von Variablen: Eine
Variable
name
der
aktuellen Shell kann durch das Kommando export(oder
durch
declare
-x) in sämtliche Tochtershells exportiert werden. Dort ist ihr
Wert dann unter dem gleichen Variablennamen bekannt. Wenn die
Tochtershell
den Wert der Variablen name ändert, bleibt diese
Änderung
ohne Einfluss auf die Muttershell. Eine exportierte Variable
verhält
sich wie ein Wertparameter in Pascal. Im Beispiel ruft das Skript test
das
Skript t2 auf, und exportiert vorher die Variable name.
Die
in t2 erfolgte Änderung von name
bleibt ohne Auswirkunge auf
die Muttershell.
|
Skript test:
re@reneu:~ > ./test
|
Verändern von Parametern: Die gesamte Parameterliste kann durch set geändert werden. Durch
Die einzige Rückmeldung findet über den Exitstatus statt, der über $? abgefragt werden kann. Jedes Kommando hat einen Exitstatus, in der Regel bedeutet $?=0, dass das Kommando fehlerfrei beendet wurde. Bei eigenen Skripten kann man den Exitstatus selbst setzen: | if ... then
echo gleich exit 1 else echo ungleich exit 51 fi |
-a file | True if file exists |
-d file | True if file exists and is a directory. |
-f file | True if file exists and is a regular file. |
-r file | True if file exists and is readable. |
-s file | True if file exists and has a size greater than zero. |
-w file | True if file exists and is writable |
-x file | True if file exists and is executable |
-O file | True if file exists and is owned by the effective user id. |
-L file | True if file exists and is a symbolic link |
-N file | True if file exists and has been modified since it was last read. |
file1 -nt file2 | True if file1 is newer (according to modification date) than file2. |
file1 -ot file2 | Older than |
Syntax
Case <ausdruck> in
|
Wielsch S 343,.»aus
/etc/profile«,
Zuweisung unterschiedlicher Prompts beim Anmelden:
Case $LOGINNAME in
|
Werteliste (aus Wielsch S 348):
Durch Blanks getrennte Aufzählung,
Suchmuster für Dateiname Kommandosubstitution implizit |
for i in Peter Kim Maria do..
for datei in *.tex do for i in ($who | cut -cl -10) do for i do (=: for i in $@ do) |
while <Kommandofolge> do <Kommandofolge> done
Beispiel (Wielsch S 350) Beim rechten Beispiel ist die Bedingung eine Kommandofolge:
read Dateiname
while [ -z »$Dateiname« ] do echo -n »Dateinamen eingeben: » read Dateiname done |
while
echo -n »Dateinamen eingeben: » read Dateiname [ -z »$Dateiname« ] do echo »FEHLER: » done |
Until-Schliefe
Mit break kann man eine, mit break n n Schleifen verlassen. Mit continue wird der aktuelle Schleifebdurchlauf wieder neu begonnen, mit continue n wird die n-te äußere Schleife im nächsten Wert begonnen. Das folgende Beispiel 4 zeigt die Wirkung:
Skript | Ausgabe beim Ablauf |
for
i in a b c d e f do
for s in 1 2 3 4 5 6 do echo $i$s read ww case $ww in n) echo "continue" continue;; q) echo "break"break;; N) echo "continue 2"continue 2;; Q) echo "break 2" break 2;; esac echo Ende des inneren Schleifenkörpers done echo Ende des äußeren Schleifenkörpers done |
re@reneu:~
> ./test
a1 w Ende des inneren Schleifenkörpers a2 n continue a3 w Ende des inneren Schleifenkörpers a4 n continue a5 w Ende des inneren Schleifenkörpers a6 w Ende des inneren Schleifenkörpers Ende des äußeren Schleifenkörpers b1 q break Ende des äußeren Schleifenkörpers c1 N continue 2 d1 Q break 2 re@reneu:~ > |
#!/bin/sh | Das Skript wird von Bash interpreteiert |
BASENAME=`basename $0`
INTERFACE=$1 DEVICE=$2 SPEED=$3 LOCALIP=$4 REMOTEIP=$5 |
Hier werden die Positionsparameterwerte
in Variablen
gesteckt,- dient sicher der besseren Lesbarkeit. (Dies wäre
auch
nötig, wenn man auf mehr als 9 Parameter zugreifen wollte:
s.o.)
Bei BASENAME eine Konkatenation aus "basename" und "$0", das $ ist unwirksam. |
if [ -z "$REMOTEIP" ]; then
echo "Usage: $0 <INTERFACE> <DEVICE> <SPEED> <LOCALIP> <REMOTEIP>" exit 1 fi |
Wenn die RemoteIP ein Leerstring ist, erfolgt Hinweis, mit welchen Parametern das Skript ip-up (der Parameter $0 wird ausgewertet) aufgerufen werden muss. Das Skript wird mit dem Exitstatus 1 abgebrochen. |
case "$INTERFACE" in
ippp*) . /etc/rc.config # find the device |
Äußeres Case.Wenn das Interface ippp0 oder ippp1 etc ist: rc.config soll abgearbeitet werden. Der vorgestellte Punkt macht die nichtausführbare Datei ausführbar. rc.config definiert Variablen, die nun in dieser Shell verfügbar sind. |
found=0
for I in $NETCONFIG; do eval NETDEV=\$NETDEV$I |
in rc.config steht: NETCONFIG=«_0 _3«, die Werteliste der Zählschleife besteht also aus durch Blanks getrennten Strings. Durch eval werden erst Variablenwerte ausgewertet und dann die Operation ausgeführt. Hier ist das erste $ gegen eval maskiert. Es wird also erst $I ausgewertet, und dieser Wert, also _0 bis 3, an den String NETDEV angehängt. Der Ergebnisstring, also etwa NETDEV_0, wird nun als Variablenname aufgefasst, und der Wert dieser (in rc.config definierten, z.B. NETDEV_1= eth0 ) Variablen wird dann NETDEV zugewiesen. |
if [ $NETDEV = $INTERFACE ]; then found=1 break; fi done |
Verzweigung mit Stringvergleich.
als Bedingung.
Es wird also geschaut, ob das INTERFACE in rc.config steht, bei Erfolg wird found auf 1 (FALSCH ???) gesetzt und die Schleife verlassen. |
if [ $found -eq 0 ]; then
echo "Device '$INTERFACE' not configured in '/etc/rc.config'" exit 1 fi |
Sic! found wird hier als Integervariable behandelt, ohne vorher als solche erklärt worden zu sein. Wenn das dem Skript übergebene INTERFACE nicht in rc.config definiert ist, Abbruch des Skripts mit Exitstatus 1 |
eval IFCONFIG=\$IFCONFIG$I | Die Variable I hat noch den Wert, mit dem sie die Schleife erfolgreich verlassen hat. Wie oben bei NETDEV wird IFCONFIG hier mit dem Inhalt von IFCONFIG_0 oder so belegt. |
DEST=`grep -v "^#" /etc/route.conf |
grep "$INTERFACE\$" | awk '{ print $1}'` |
Eine schöne Pipe: Gib alle Zeilen,
die NICHT
(-v) mit # anfangen (^), der Datei /etc/route.conf aus. Suche in diesen
Zeilen die mit dem Inhalt der Variablen INTERFACE (also ippp0...) am
Zeilenende ($, maskiert) Aus dieser Zeile nimmt das Programm
awk das erste Feld und gibt es aus, d.h., es wird der Variablen DEST
zugewiesen.
DEST erhält somit die IP-Nummer, die dem Interface in route.conf
zugeordnet
ist.
(Diese 3 Anweisungen müssen eigentlich in einer Zeile stehen.) |
DEFAULT=`grep -v "^#" /etc/route.conf |
grep default | awk '{ print $2}'` |
In gleicher Manier erhält die Variable DEFAULT das 2. Feld der Zeile mit dem default-Eintrag, also die Gateway-IP-Nummer (Sie stimmt in der Regel mit der IP-Nummer des Interfaces überein). |
#echo "ok, NETDEV:$NETDEV;IFCONFIG:$IFCONFIG."
#echo " DEST: $DEST; DEFAULT: $DEFAULT" |
Debugeinträge, um zu prüfen, ob alle Zuordnungen stimmen,- auch, ob die Konfiguration in rc.config mit route.conf harmoniert. Hier jetzt auskommentiert. |
case "$BASENAME" in
ip-up) /sbin/route add default gw $REMOTEIP dev $INTERFACE |
Inneres Case: Falls das
Skript mit
ip-up
aufgerufen wurde: wird die defaultroute gesetzt.
Anscheinend soll dieses Skript den Rechner in Standby-Modus versetzen, eine Verbindung wird hier ja nicht aufgebaut. (Ich meine, hier müssten ;; stehen, - Ende eines Case-Falles) |
ip-down)
# restart interface /sbin/ifconfig $INTERFACE down # workaround due to kernel problem with 'kernd': sleep 1 |
Falls das Skript (über einen Symbolischen Link) mit
ip-down
aufgerufen
wurde: wird ifconfig ippp0 downaufgerufen
warten (??) |
/sbin/ifconfig $INTERFACE $IFCONFIG | ippp0 wird wieder gestartet, mit den Angaben der IP-Nummern, pointopoint etc... So wird der Rechner vermutlich wieder in Standby-Modus gesetzt. |
# set routes from /etc/route.conf
test -z "$DEST" || /sbin/route add -host $DEST dev $INTERFACE |
Wenn DEST nicht leer ist (es sollte die Remote-IP-Nummer zu ippp0 enthalten), dann wird die Route zur Remote-IP-Nummer gesetzt (Ich meine mal irgendwo gelesen zu haben, dass ifconfig down die Routen entfernt, deshalb werden sie hier wohl wieder neu gesetzt. |
test -z "$DEFAULT" || /sbin/route add default gw $DEFAULT ;; | Wenn DEFAULT nicht leer ist, wird das Defaultgateway wieder gesetzt. (allerdings nur die IP-Nummer, es wird im Unterschied zu ip-up kein Device angegeben. Was dieser Unterschied bedeutet, weiss ich nicht). |
*) ;;
> Übertragung unterbrochensp; esac ;; |
Sonst: Nichts tun.
Ende des inneren CASE (ip up / down ) Das doppelte Semikolon, weil hiermit ein Fall des äußeren CASE beendet ist. |
ppp*)
# Analog-PPP, add commands if you need... ;; |
Zweiter Fall zu INTERFACE-CASE |
*) # dont know... ;; | sonst |
esac | Ende des äußeren CASE. |
exec 5<>tete echo peter>&5 cat<&5 exec 5>6_ |
weist die Datei mit Namen tete dem Despriptor 5 zu, und zwar zum Lesen und Schreiben. Mit echo und redirekt kann man in die datei schreiben, es wird anscheinend hinten angehängt. So wird der Dateiinhalt auf dem Bildschirm ausgegeben. Komischerweise klappt das nur einmal, während echo mehrmals hintereinander geht. Beenden der Verbindung. |
So, und nun das Spannende (vorausgesetzt natürlich, dass Verbindung zum Internet besteht).: | |
exec 5<>/dev/tcp/www.heise.de/80 echo "GET /">&5 cat<&5 exec 5>&- |
Verbindet 5 mit Heise.de zum Lesen und schreiben. Schreibt in diesen Kanal, also an heise.de, das Kommando GET /, was wohl ein http-Kommando ist. Schreibt das, was der heise-Server daraufhin in den Kanl getan hat, also die Antwort, auf den Bildschirm. Beenden der Verbindung. |
Jetzt wollte ich von Heise gesendete HTML-Seite nicht nur flüchtig auf dem Bildschirm haben. Das ging mit Änderung der cat-Zeile | |
cat<&5 | cat>tete | Pipe: Die Ausgabe von cat<&5 wird in cat>tete umgeleitet, und so ist nun die html-Seite in der datei tete gespeichert! |
Filedeskriptoren werden auch von read benutzt. Auf folgende Art konnte ich den Inhalt einer Datei in eine Arrayvariable einlesen. | |
declare -a feld echo "ma mi mu me mo">datei exec 6<>datei read -u 6 -a feld echo ${feld[3]} me exec 6>&- |
Die Datei datei muss mit einem Filedeskriptor verbunden
werden. DercParameter -u von read erlaubt die Angabe eines anderen
Kanals (Filedeskriptor), aus dem gelesen wird. Dieser Readbefehl geht
aber, wie oben auch das cat, nur einmal "im Leben" des Deskriptors.
Woeso, ist mir nicht klar. Laut Dölle kann man, bei entsprechender Belegung von IFS mit Semikola etc, eine ganze CSV-Datei in eine Feldvariable einlesen und dann mit Stringoperatonen bearbeiten. |
2 Die Systemvariable PATH speichert den Suchpfad, in welchem das System nach einem Programm sucht. Sie kann für jeden Benutzer individuell festgelegt werden. In der Regel befindet sich das aktuelle Verzeichnis nicht im Suchpfad,- Unterschied zu DOS.
3 Man-Page, ausführlich Manual-Page-, ist eine Form der in jedem Linuxsystem vorhandenen Dokumentation. Durch die Eingabe von "man bash" wird die Manual-Page zur Bash ausgegeben. Über die Man-Pages hinaus gibt es noch weitere Dokumentationssysteme in Linux, etwa das Texinfo-System. Die Man-Page zur Bash ist mehre tausend Zeilen lang.
4 Die Darstellung der Syntax und Optionen dieser beiden Befehle sprengt den Rahmen dieser Arbeit. Sie sind in jeder Befehlsreferenz enthalten, etwa in Wielsch oder Boes/Reimann
5 Nach Boes/Reimann S310: ". datei: Kommandos werden aus der Datei datei gelesen und ausgeführt.[...] Ausführung geschieht in aktueller Shell (kein neuer Prozess)."
6 Wielsch S 318 f:
"In der Shell können Variablen auch Datentypen haben. Nun kann
über
das Kommando declare eine Variable mit dem Attribut integer
ausgerüstet
werden. Die Berechnungen mit solchen ganzzahligen Variablen gehen
natürlich
schneller vonstatten." Ich hatte das so verstanden, dass die
Integervariable
deklariert werden muss,- dem scheint nicht so zu sein."
Zurück zur Homepage von R.Lütticken |