7. Shell-Programm, Programmiermodell und Lösungen
7.1 Wohin mit dem Programm – geeignete Ablageorte
Es ist ein ungeschriebenes Gesetz, dass Distributionen die Verzeichnisse unter /opt und /usr/local nicht anrühren.
Root:
Root sollte (z. B. aus dem Internet herunter geladene) Drittanbieter-Programme unter /opt installieren und nach /usr/local/bin (für normale Benutzer) oder /usr/local/sbin (Systemprogramme) verlinken.
Root sollte eigene Programme unter /usr/local/bin (für normale Benutzer) oder /usr/local/sbin (Systemprogramme) ablegen.
Die Shell-Variable PATH enthält standardmäßig /usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin
. Mit dieser Suchreihenfolge werden (bei Programmen mit gleichen Namen) zuerst die eigenen, dann die (nach-)installierten, dann die Standard-Programme gefunden.
Andere Benutzer:
Nach dem Freedesktop-Standard ist $HOME/.local synonym zu /usr/local. Benutzer sollten eigene Programme unter $HOME/.local/bin ablegen und dieses Verzeichnis in der Shell-Variablen PATH angeben.
PATH=“~/.local/bin:/usr/local/bin:/usr/bin:/bin“
Dazugehörige Bibliotheken, Dokumentationen und Quelltexte (für Compiler) liegen dann unter $HOME/.local/lib, $HOME/.local/doc und $HOME/.local/src.
7.2 Die erste Zeile – Angabe des Interpreters
Hinweis: Dies ist eine Funktion des Kernels!
Ist eine Textdatei ausführbar, und beginnt die erste Zeile mit „#!“, wird alles was in der Zeile dahinter steht als zu verwendendes Interpreter-Programm interpretiert.
Beispiele:
#!/bin/sh
# Standard-Shell
#!/usr/bin/bash
# GNU Bash
#!/usr/bin/awk -f
# AWK-Programm
#!/usr/bin/bc -l
# BC-Programm
#!/usr/bin/sed -f
# SED-Programm
7.3 Programmiermodell
7.4 Mehrfachausführung verhindern – Lock-Datei
Unix ist ein Multitasking-System, es können beliebig viele Programme gleichzeitig ausgeführt werden. Soll ein Programm während der Ausführung den erneuten Aufruf verhindern, arbeitet es am besten mit Lock-Dateien.
Die Namenskonvention für Geräte-Lock-Dateien ist LCK.<device>
, wobei <device> der Gerätename im Dateisystem ist.
Es wird das Format der HDU-UUCP-Lock-Dateien verwendet. Das heißt, Lock–Dateien enthalten eine Prozess-ID als 10-Byte-ASCII-Dezimalzahl (führende Leerzeichen), gefolgt von einem Zeilenumbruch.
Root:
Root sollte Lock-Dateien unter /var/local/lock speichern lassen.
Andere Benutzer:
Nach dem Freedesktop-Standard ist $HOME/.cache synonym zu /var. Benutzer können für Lock-Dateien das Verzeichnis $HOME/.cache/local/lock anlegen.
Beispiel für Lock-Datei schreiben:
mkdir -p /var/local/lock
printf „%10s“ $$ > /var/local/lock/$0.lock
echo » /var/local/lock/$0.lock
Beispiel für Lock-Datei erkennen und mit (frei erfundenem) Exit-Code >0 beenden:
[ -r “/var/local/lock/programmname.lock“ ] && exit 16
7.5 Konfiguration
Die Summe aller sichtbaren Shell-Variablen nennt man Umgebung. Diese Umgebung lässt sich üblicherweise in der Reihenfolge Programmierer ⇒ Root ⇒ Benutzer konfigurieren. Also:
Für den Programmablauf notwendige Variablen werden vom Programmierer vorbelegt.
Root kann diese Umgebung überschreiben oder zusätzliche Variablen festlegen. Die dazugehörigen Dateien liegen unter /etc/local.
Nach dem Freedesktop-Standard ist $HOME/.config synonym zu /etc. Benutzer können die Umgebung von Root individuell überschreiben oder zusätzliche Variablen festlegen, abgelegt z.B. unter $HOME/.config/local.
Beispiel:
Konfigurationsdatei /etc/local/prog.cfg von Root:
var1=“Wert von Root“
var2=“noch ein Wert von Root“
Konfigurationsdatei $HOME/.config/local/prog.cfg vom Benutzer:
var1=“Wert vom Benutzer“
var3=“noch ein Wert vom Benutzer“
Shell-Programm:
#!/bin/sh var1=“Wert vom Programmierer“
[ -r “/etc/local/prog.cfg“ ] && . /etc/local/prog.cfg
[ -r “~/config/local/prog.cfg“ ] && . /etc/local/prog.cfg
export var1
[ -n “$var2“ ] && export var2
[ -n “$var3“ ] && export var3
7.6 Wer führt das Programm aus? – Root oder Benutzer
Der Benutzer root hat per Definition die User-ID 0. Diese lässt sich mit dem Programm id abfragen.
if [ “$(id -u)“ = “0“ ] ; then
# Programmteil für Root
else
# Programmteil für andere Benutzer
fi
7.7 Kommandozeilenparameter und Auswertung
Jedes Programm hat ein Standard-Verhalten. Dieses Standard-Verhalten wird üblicherweise so gewählt, dass das Programm gefahrlos gestartet / ausprobiert werden kann, oft erfolgt lediglich die Ausgabe einer Meldung.
Abweichungen von diesem Standard-Verhalten erfordern zusätzliche optionale Angaben, die dem Programm dann über die Kommandozeilenparameter mitgeteilt werden.
Beispiel: Programm für die Steuerung des SSH Server-Dienstes:
#!/bin/sh
case $1 in
start)
if [ “$(id -u)“ = “0“ ] ; then
systemctl start ssh
else
echo “Es sind Root-Rechte erforderlich.“ >&2
fi
;;
stop)
if [ “$(id -u)“ = “0“ ] ; then
systemctl stop ssh
else
echo “Es sind Root-Rechte erforderlich.“ >&2
fi
;;
*)
systemctl status ssh
echo
echo „Benutzung: rc.ssh start|stop“
;;
esac
Dieses Beispiel-Programm gibt nur Meldungen aus, wenn kein Parameter „start“ oder „stop“ angegeben, oder wenn das Programm nicht mit Root-Rechte gestartet wurde.
7.7.1 Auswertung mit getopts-Kommando
Beispiel:
# Hier wird "$@" verwendet, um die Kommandozeilenparameter in einzelne # Wörter zerlegt zu übergeben. TEMP=$(getopt -o 'ab:c::' –long 'a-long,b-long:,c-long::' -n 'example.bash' – "$@") if [ $? -ne 0 ]; then echo 'Beende mit Fehler…'>&2 exit 1 fi eval set – "$TEMP" unset TEMP while true; do case "$1" in '-a'|'–a-long') echo 'Option a' shift continue ;; '-b'|'–b-long') echo "Option b, Argument '$2'" shift 2 continue ;; '-c'|'–c-long') # c hat ein optionales Argument. case "$2" in '') echo 'Option c, kein Argument' ;; *) echo "Option c, Argument '$2'" ;; esac shift 2 continue ;; '–') shift break ;; *) echo 'Internal error!'>&2 exit 1 ;; esac done echo 'Übrig gebliebene Argumente:' for arg; do echo "–> '$arg'" done
7.8 Daten ein-/ausgeben, Fehlermeldungen
Für alle Arten von Nutzdaten sind die 3 Kanäle stdin (Standard-Eingabe Kanal 0), stdout (Standard-Ausgabe Kanal 1) und stderr (Standard-Fehlerausgabe Kanal 2) vorgesehen. Nur das Vordergrund-Programm ist normalerweise mit Tastatur und Display verbunden. Alle anderen Hintergrund-Programme nehmen keine Eingaben von der Tastatur entgegen, Ausgaben gehen normalerweise zum Pseudo-Gerät /dev/null. Diese normalen Verbindungen können von / nach Dateien umgelenkt werden.
7.8.1 Dateneingabe
Aufgerufene Kommandos lesen Daten von der Standard-Eingabe.
Das Programm read kann während der Programmausführung Daten in Shell-Variablen einlesen.
read var
echo $var
Mit dem Programm cat lässt sich ein einfacher zeilenorientierter Texteditor realisieren:
cat > Datei.txt « „EOF„
Schreibt gesamten Text zeilenweise in die Datei
bis das Label EOFalleinstehend erkannt wird.
Innerhalb der Zeile kann auch noch korrigiert werden.
EOF
7.8.2 Datenausgabe und Fehlermeldungen
Aufgerufene Kommandos schreiben Nutzdaten in die Standard-Ausgabe.
Die Shell kann über das Pipe-Zeichen „|“ die Ausgabe eines Kommandos mit der Standard-Eingabe des nächsten Kommandos verbinden. Fehlermeldungen sollten über den dafür vorgesehenen Kanal 2 ausgegeben werden, damit diese von dem per Pipe verbundenen Kommando nicht versehentlich als Nutzdaten interpretiert werden.
Hinweis: Die Ausgabe auf dem Display unterscheidet sich von Datenumlenkung oder Pipe-Verbindung.
Beispiele zum ausprobieren:
echo Datenausgabe auf Display
echo Fehlerausgabe auf Display >&2
echo -n „Fehler-“ >&2 ; echo „Ausgabe auf Display“
Auf dem Display lassen sich die beiden Ausgabekanäle nicht wirklich unterscheiden.
Schreibe nun die letzte Zeile in ein Shell-Skript ~/.local/bin/test.sh
#!/bin/sh
echo -n „Fehler-“ >&2 ; echo „Ausgabe auf Display“
Teste das Programm auf verschiedene Arten und prüfe Ausgabe auf Display und in Datei:
test.sh
test.sh > ~/test.txt
test.sh 2> ~/test.txt
test.sh 2> ~/test.txt » test.txt
7.9 Erneuter Programmstart ermöglichen – Laufzeitdaten
Laufzeitdaten sind Daten, die während des Programmablaufs entstehen, und in Variablen gespeichert sind. Bei einem unerwarteten Programmabsturz gehen diese Daten verloren, damit ist der Programm-Fortschritt nicht mehr eindeutig. Ein erneuter Programmstart ist u. U. mit Problemen verbunden.
Der geeignete Ablageort für Laufzeitdaten richtet sich nach der vorgesehenen Gültigkeit der Daten.
Bis zum erfolgreichen Programmablauf gültige temporäre Daten, die nicht den Rechner-Neustart überleben müssen, lassen sich von allen Benutzern unter /tmp ablegen.
Bis zum erfolgreichen Programmablauf gültige temporäre Daten, die auch den Rechner-Neustart überleben sollen, lassen sich von allen Benutzern unter /var/tmp ablegen.
Root:
Root kann auf unbestimmte Zeit gültige, auch von anderen Programmen nutzbare, Laufzeitdaten unter /var/local/lib ablegen.
Andere Benutzer:
Benutzer können bis zur Abmeldung (Logout) kurzfristig gültige Daten unter /run/user/$(id -u) ablegen.
Benutzer können auf unbestimmte Zeit gültige, auch von anderen Programmen nutzbare, Laufzeitdaten unter $HOME/.cache/local/lib ablegen.
7.10 Fortschrittsmeldungen protokollieren – Log-Datei
Fortschrittsmeldungen enthalten Informationen zum Programmfortschritt, und sollten in eine Datei protokolliert werden.
Root:
Root kann Log-Dateien unter /var/local/log ablegen.
Andere Benutzer:
Benutzer können Log-Dateien unter $HOME/.cache/local/log ablegen.
7.11 Parallele Prozesse synchronisieren – wait, kill, trap
7.11.1 wait-Kommando
Um 2 oder mehr Prozesse einander zu synchronisieren können Lock-Dateien (siehe 7.4) eingesetzt werden. Diese enthalten die für das wait-Kommando benötigte Prozess-ID des parallelen Prozesses.
if [ -r “/var/local/lock/programmname.lock“ ]; then
ppid=$(echo $(cat test.txt))
wait $ppid
fi
Das wait-Kommando wartet solange, bis der angegebene Prozess beendet ist.
Anderes Beispiel:
#!/bin/sh # fluxbox startup-script: # Deutsche Tastaturbelegung xmodmap "/home/rudi/.Xmodmap" setxkbmap -model evdev -layout de # Fluxbox starten exec fluxbox & wmpid=$! sleep 1 { # Autostart-Programme volumeicon & nm-applet & fdpowermon & } & # Haltepunkt. Programm darf erst enden, wenn Fluxbox beendet wurde wait $wmpid
7.11.2 kill und trap
Das kill-Kommando kann Signale zu anderen Prozessen senden. Die Signale SIGUSR1 und SIGUSR2 haben die Nummern 10 und 12, und können frei verwendet werden.
if [ -r “/var/local/lock/programmname.lock“ ]; \then
ppid=$(echo $(cat test.txt))
kill -10 $\ppid
fi
Das angesprochene Programm kann mit dem trap-Kommando darauf reagieren:
while true ; \do
trap break \10
done
7.11.3 Liste von Signale und deren Nummer
1 | SIGHUP | Wiederherstellung der Kontrolle, wenn Prozess sich aufgehängt hat |
2 | SIGINT | Strg-c |
3 | SIGQUIT | Strg-d |
4 | SIGILL | |
5 | SIGTRAP | |
6 | SIGABRT | |
7 | SIGBUS | |
8 | SIGFPE | Signal wenn eine unerwartete mathematische Operation ausgeführt wurde |
9 | SIGKILL | Prozess sofort beenden |
10 | SIGUSR1 | Benutzerdefiniertes Signal |
11 | SIGSEGV | |
12 | SIGUSR2 | Benutzerdefiniertes Signal |
13 | SIGPIPE | |
14 | SIGALRM | Signal für Alarm |
15 | SIGTERM | Prozess beenden |
16 | SIGSTKFLT | |
17 | SIGCHLD | Prozess anhalten |
18 | SIGCONT | |
19 | SIGSTOP | Prozess anhalten |
20 | SIGTSTP | |
21 | SIGTTIN | |
22 | SIGTTOU | |
23 | SIGURG | Prozess anhalten |
24 | SIGXCPU | |
25 | SIGXFSZ | |
26 | SIGVTALRM | |
27 | SIGPROF | |
28 | SIGWINCH | |
29 | SIGIO | |
30 | SIGPWR | |
31 | SIGSYS | |
34 | SIGRTMIN | |
35 | SIGRTMIN+1 | |
36 | SIGRTMIN+2 | |
37 | SIGRTMIN+3 | |
38 | SIGRTMIN+4 | |
39 | SIGRTMIN+5 | |
40 | SIGRTMIN+6 | |
41 | SIGRTMIN+7 | |
42 | SIGRTMIN+8 | |
43 | SIGRTMIN+9 | |
44 | SIGRTMIN+10 | |
45 | SIGRTMIN+11 | |
46 | SIGRTMIN+12 | |
47 | SIGRTMIN+13 | |
48 | SIGRTMIN+14 | |
49 | SIGRTMIN+15 | |
50 | SIGRTMAX-14 | |
51 | SIGRTMAX-13 | |
52 | SIGRTMAX-12 | |
53 | SIGRTMAX-11 | |
54 | SIGRTMAX-10 | |
55 | SIGRTMAX-9 | |
56 | SIGRTMAX-8 | |
57 | SIGRTMAX-7 | |
58 | SIGRTMAX-6 | |
59 | SIGRTMAX-5 | |
60 | SIGRTMAX-4 | |
61 | SIGRTMAX-3 | |
62 | SIGRTMAX-2 | |
63 | SIGRTMAX-1 | |
64 | SIGRTMAX |
7.12 Programm beenden – Exit-Status
Wenn ein Programm erfolgreich endet, liefert es den Exit-Status 0 (Voreinstellung), ansonsten einen Wert > 0.
exit
exit n
Das gleiche gilt für die Shell-Funktion (Rückgabewert), denn die Shell-Funktion ist (nichts weiter als) ein Shell-Programm eingebettet in ein Shell-Programm.
Der Exit-Status kann mit $? abgefragt werden
echo “$?“
Intern ist der Exit-Status ein 8-Bit-Wert. Damit können je Programm bis zu 255 verschiedene Fehlermeldungen frei zugewiesen werden.
7.12.1 Liste häufig verwendeter Exit-Codes
0 | Success | 68 | Advertise error | |
1 | Operation not permitted | 69 | Srmount error | |
2 | No such file or directory | 70 | Communication error on send | |
3 | No such process | 71 | Protocol error | |
4 | Interrupted system call | 72 | Multihop attempted | |
5 | Input/output error | 73 | RFS specific error | |
6 | No such device or address | 74 | Bad message | |
7 | Argument list too long | 75 | Value too large for defined data type | |
8 | Exec format error | 76 | Name not unique on network | |
9 | Bad file descriptor | 77 | File descriptor in bad state | |
10 | No child processes | 78 | Remote address changed | |
11 | Resource temporarily unavailable | 79 | Can not access a needed shared library | |
12 | Cannot allocate memory | 80 | Accessing a corrupted shared library | |
13 | Permission denied | 81 | .lib section in a.out corrupted | |
14 | Bad address | 82 | Attempting to link in too many shared libraries | |
15 | Block device required | 83 | Cannot exec a shared library directly | |
16 | Device or resource busy | 84 | Invalid or incomplete multibyte or wide character | |
17 | File exists | 85 | Interrupted system call should be restarted | |
18 | Invalid cross-device link | 86 | Streams pipe error | |
19 | No such device | 87 | Too many users | |
20 | Not a directory | 88 | Socket operation on non-socket | |
21 | Is a directory | 89 | Destination address required | |
22 | Invalid argument | 90 | Message too long | |
23 | Too many open files in system | 91 | Protocol wrong type for socket | |
24 | Too many open files | 92 | Protocol not available | |
25 | Inappropriate ioctl for device | 93 | Protocol not supported | |
26 | Text file busy | 94 | Socket type not supported | |
27 | File too large | 95 | Operation not supported | |
28 | No space left on device | 96 | Protocol family not supported | |
29 | Illegal seek | 97 | Address family not supported by protocol | |
30 | Read-only file system | 98 | Address already in use | |
31 | Too many links | 99 | Cannot assign requested address | |
32 | Broken pipe | 100 | Network is down | |
33 | Numerical argument out of domain | 101 | Network is unreachable | |
34 | Numerical result out of range | 102 | Network dropped connection on reset | |
35 | Resource deadlock avoided | 103 | Software caused connection abort | |
36 | File name too long | 104 | Connection reset by peer | |
37 | No locks available | 105 | No buffer space available | |
38 | Function not implemented | 106 | Transport endpoint is already connected | |
39 | Directory not empty | 107 | Transport endpoint is not connected | |
40 | Too many levels of symbolic links | 108 | Cannot send after transport endpoint shutdown | |
42 | No message of desired type | 109 | Too many references | |
43 | Identifier removed | 110 | Connection timed out | |
44 | Channel number out of range | 111 | Connection refused | |
45 | Level 2 not synchronized | 112 | Host is down | |
46 | Level 3 halted | 113 | No route to host | |
47 | Level 3 reset | 114 | Operation already in progress | |
48 | Link number out of range | 115 | Operation now in progress | |
49 | Protocol driver not attached | 116 | Stale file handle | |
50 | No CSI structure available | 117 | Structure needs cleaning | |
51 | Level 2 halted | 118 | Not a XENIX named type file | |
52 | Invalid exchange | 119 | No XENIX semaphores available | |
53 | Invalid request descriptor | 120 | Is a named type file | |
54 | Exchange full | 121 | Remote I/O error | |
55 | No anode | 122 | Disk quota exceeded | |
56 | Invalid request code | 123 | No medium found | |
57 | Invalid slot | 125 | Operation canceled | |
59 | Bad font file format | 126 | Required key not available | |
60 | Device not a stream | 127 | Key has expired | |
61 | No data available | 128 | Key has been revoked | |
62 | Timer expired | 129 | Key was rejected by service | |
63 | Out of streams resources | 130 | Owner died | |
64 | Machine is not on the network | 131 | State not recoverable | |
65 | Package not installed | 132 | Operation not possible due to RF-kill | |
66 | Object is remote | 133 | Memory page has hardware error | |
67 | Link has been severed |
8. Unix-Textverarbeitung
col
umgekehrte Zeilenvorschübe aus der Eingabe filtern
comm
Zwei sortierte Dateien Zeile für Zeile vergleichen
cut
Teile jeder Zeile aus Dateien entfernen
diff
Dateien zeilenweise vergleichen
grep
gibt Zeilen aus, die zu Suchmustern passen
egrep
fgrep
expand
Tabulatoren in Leerzeichen umwandeln
fold
jede Eingabezeile so umbrechen, dass sie in eine angegebene Breite passt
gettext
kann Nachrichten übersetzen
head
den ersten Teil von Dateien ausgeben
iconv
Zeichenkodierung eines Texts in eine andere umwandeln
join
Zeilen von zwei Dateien über ein gemeinsames Feld verbinden
nl
Zeilen von Dateien nummerieren
od
Dateien im Oktal- und anderen Formaten ausgeben
paste
Zeilen von Dateien zusammenfügen
printf
Daten formatieren und ausgeben
seq
eine Zahlenfolge ausgeben
sort
Zeilen von Textdateien sortieren
tail
Den letzten Teil von Dateien ausgeben
tee
Von Standardeingabe lesen und in Standardausgabe und Dateien schreiben
tr
Zeichen umwandeln oder löschen
tsort
topologisch sortieren
unexpand
Leerzeichen in Tabulatoren umwandeln
uniq
doppelte Zeilen berichten oder entfernen
wc
Anzahl der Zeilen, Wörter und Byte für jede Datei ausgeben
9. Programmierwerkzeuge
9.1 Rechnersprache bc
Die POSIX Standard-Shell kann nur Zeichenketten numerisch vergleichen (>, <, =, …), kennt jedoch keinerlei numerische Berechnungen. Selbst für das einfache hoch oder runter zählen von Zahlen z. B. in Programmschleifen werden externe Programme wie bc oder awk als Unix-Werkzeuge benötigt.
bc (binary calculator) ist eine interaktive Algebra–Sprache mit nahezu beliebiger Festkomma-Genauigkeit.
Hier wird die POSIX Standard-Version von bc vorgestellt.
bc unterstützt:
unterschiedliche Zahlenbasis für Eingabe (2 … 16) und Ausgabe (2 … 999)
je 26 Namen (a … z) für Variablen, eindimensionale Arrays, Funktionen
Grundrechenarten, Zuweisungen +, -, *, /, %, ^, sqrt(), =, +=, -=, *=, /=, %=, ^=
Postinkrement/-dekrement, Preinkrement/-dekrement (++, –)
Vergleiche ( <, >, ==, !=, ⇐, >=, == )
optionale Funktionen sin s(x), cos c(x), arctan a(x), ln l(x), ex e(x), Bessel-Funktion j(n,x)
Programmablaufsteuerung if, while, for, break, return, quit, /* … */
Ausgabe zusätzlicher Informationen “string“
Beispiel: Berechnung und Zuweisung von π. (Hinweis: arctan( 1 ) = 45° oder π / 4)
pi=$(echo „scale=10; 4 * a( 1 )“ | bc -l)
9.2 Datum/Uhrzeit date
date zeigt / ändert die Systemzeit, und unterstützt dabei Zeitzonen und die Umrechnung nach Unix-Zeit (Anzahl Sekunden seit 1.1.1970 00:00) und zurück.
Das Programm date enthält auch eine Mini-Programmiersprache für das Rechnen mit Terminen.
Beispiele:
Wie lautet die jetzt aktuelle Unix-Zeit?
date +%s
Wie lautet die Unix-Zeit um 04:35 Uhr am nächsten Samstag?
date –date='04:35 next Sat' +%s
9.3 Stream–Editor Sed
sed ist ein Stream-Editor für die grundlegende Texttransformationen auf einen Eingabestrom (einer Datei oder aus einer Verarbeitungskette). Sed kann z. B. Text anhängen, einfügen, löschen, suchen und ersetzen. Gesucht wird mittels regulären Ausdrücken.
Mehrere Sed-Kommandos können aus einem Programmskript eingelesen werden. Dabei werden auch Kommentarzeilen, Label, unbedingte und bedingte Sprünge (falls eine Textstelle ersetzt werden konnte) unterstützt.
Beispiele:
# lösche in einer Datei die 4. Zeile
$ sed '4d' input.txt > output.txt
# ersetze in den Zeilen 10-20 jedes Vorkommen von 'hello' durch 'world'
$ sed '10,20s/hello/world/' input.txt > output.txt
9.4 Interpreter für AWK
mawk ist ein Interpreter für die Programmiersprache AWK, benannt nach den Anfangsbuchstaben der Entwickler (Aho, Weinberger und Kernigham). Die Sprache AWK ist nützlich zur Manipulation von Daten, für die Texterstellung und -verarbeitung und für Prototypen und Experimente mit Algorithmen.
AWK wertet Eingaben im Prinzip immer als eine Tabelle aus, welche sich in Zeilen und Spalten zerlegen lässt. Die Trennzeichen für Zeilen und Spalten können frei gewählt werden.
Wie arbeitet AWK? - Programmiermodell
Ein AWK-Programm besteht aus 4 Teile: BEGIN-Teil, Bedingungen, Verarbeitungsteil und END-Teil.
Alle 4 Teile sind optional, es muss lediglich wenigstens ein Teil vertreten sein.
1. BEGIN { Anweisungen }
Dieser Teil wird als erstes ausgeführt, hier stehen Initialisierungen, Funktionsdefinitionen, …
2. Einlesen der nächsten Eingabezeile, zerlegen in Spalten gemäß Trennzeichen, und ablegen in die
Variablen $1 … $9. In $0 steht die gesamte Eingabezeile.
3. Bedingung1 { Anweisungen }
Bedingung2 { Anweisungen }
…
Verarbeitungsteil. Wenn eine (optionale) Bedingung auf die eingelesenen Daten zutrifft, wird der dazugehörige Anweisungsblock ausgeführt.
Dieser Programmteil wird nach jeder Eingabezeile erneut ausgeführt.
4. Noch eine Eingabezeile vorhanden? Wenn ja dann weiter bei 2., wenn nein dann …
5. END { Anweisungen }
Dieser Teil wird zuletzt ausgeführt, und dient meistens der Ausgabe der berechneten Ergebnisse.
AWK–Programme haben eine C–ähnliche Syntax und sind auf Grund ihrer besonderen Datenverarbeitungs-Struktur meist deutlich kürzer als in anderen Programmiersprachen. Die Sprache AWK unterstützt:
2 grundlegende Datentypen: Numerisch und Zeichenkette, sowie Arrays, autom. Typzuweisung
Verzweigungen: if …, if … else …
Programmschleifen: while …, do … while …, for …, continue, break
benutzerdefinierte Funktionen: function name() { Anweisungen; return }
Regulärer Ausdruck als Bedingung: / regex /
Aus C bekannte Zuweisungen und Operatoren
String-Funktionen
Funktionen für Datum und Zeit
Mathematische Funktionen: sin(), cos(), atan2(), log(), exp(), int(), rand(), sqrt()
Weitere Eingabekanäle z. B. aus Dateien
(Formatierte) Ausgabe nach stdout, stderr, in Datei
Zugriff auf Shell-Umgebungsvariablen
Zugriff auf Kommandozeilenparameter des AWK-Interpreters
Ausführung von Kommandos in Sub-Shell
Beispiel für ein kurzes AWK-Programm:
Aufgabe: Welche Nummer 1 … 6 hat das aktuelle Terminal?
Lösung: Das Kommando tty liefert /dev/tty1 … /dev/tty6. Links von der gewünschten Ziffer steht der Buchstabe ‚y‘, der nur einmal vorkommt. Mit ‚y‘ als Trennzeichen ergibt sich eine „Tabelle“ mit 1 Zeile und 2 Spalten. Die 1. Spalte enthält immer „/dev/tt“, die 2. Spalte soll ausgegeben werden.
TTYnr=$(tty | awk -F „y“ – '{ print($2) }')
9.5 Mathematische Konstanten und abgeleitete Formeln
gedacht für die Anwendung in bc oder awk.
Konstanten:
π = 3.141592653589793238462643383279502884197169399375105820974944592
e = 2.718281828459045235360287471352662497757247093699959574966967627
π = 4 * arctan 1
e = e 1
Logarithmische und Exponential-Funktionen
log b x = ln x / ln b
lg x = ln x / ln 10
y x = e x * ln y
10 x = 10 ^ x = e x * ln 10
Trigonometrische und Arcus-Funktionen (in Radiant)
tan x = sin x / cos x
cot x = cos x / sin x
arcsin x = arctan(x / sqr(1 – x 2 ) )
arccos x = π / 2 - arctan(x / sqr(1 – x 2 ) )
arccot x = 1 / arctan x
x (in Grad) = x (in Radiant) * 180 / π
Polare (r, φ) und rechtwinklige (x, y) Koordinaten
r = srq(x 2 + y 2 )
φ = arctan(y / x)
x = r * cos φ
y = r * sin φ
Hyperbolische und Area-Funktionen
sinh x = (e x – e -x ) / 2
cosh x = (e x + e -x ) / 2
tanh x = (e x – e -x ) / (e x + e -x )
coth x = (e x + e -x ) / (e x – e -x )
arsinh x = ln(x + sqr(x 2 +1) )
arcosh x = ln(x + sqr(x 2 – 1) )
artanh x = ln( (1 + x) / (1 - x) ) / 2
arcoth x = ln( (x + 1) / (x - 1) ) / 2
9.6 Makroprozessor m4
m4 ist eine Implementierung des traditionellen UNIX–Makroprozessors.
Ein Makroprozessor enthält eine Mini-Programmiersprache für Textersetzungen anhand gegebener Definitionen. Dabei werden die Eingabedaten entsprechend erweitert / verändert zur Ausgabe durchgeleitet (Vergleiche auch: C Präprozessor).
M4 unterstützt
Makro-Definitionen, mit Parameter,
umbenennen, entfernen, neu definieren von Makros
Mathematische Grundrechenarten, Bitweise Logik, Vergleichs- und logische Operatoren
Verzweigungen: ifdef, ifelse
Programmschleifen: forloop, foreach
Rekursion: shift
Arbeiten mit Datenablage auf Stack
Definieren von berechneten Makros mit Makros: composite
String-Funktionen, Suchen mit regulärem Ausdruck, String formatieren
Öffnen weiterer Kanäle für Eingabedaten aus Dateien
Ausführung von Kommandos in Sub-Shell und Nutzung deren Ausgabe als Eingabedaten
und stellt damit eine vollständige Programmiersprache für jedes noch so komplexe Problem bereit.
Beispiel: Mit bc Flächeninhalt eines Kreises A = r 2 * π berechnen
Eigener Programmtext in kreis.src:
Flaeche = POT(2,Radius)*PI
Makrodefinitionen für m4 in bc.m4. ‚$1‘ und ‚$2‘ sind hier Makroparameter.
define(POT, (e($1*l($2))))
define(Flaeche, a[1])
define(Radius, r)
define(PI, 3.1416)
Mit m4 Programmtext für bc nach kreis.bc übersetzen
m4 bc.m4 kreis.src > kreis.bc
⇒ Inhalt von kreis.bc
a[1] = (e(2*l(r ))*3.1416
9.7 C Entwicklungsumgebung
Programmierung in Assembler
Die 2. Generation der Programmiersprachen stellen die Assemblersprachen dar. Hierbei werden den binären oder hexadezimalen Maschinencodes leichter merkbare Kürzel, so genannte „Mnemonics“, zugeordnet, welche vom Assembler in Maschinencode übersetzt werden.
Zumindest ein Prozessorspezifischer Assembler wird üblicherweise vom Chip-Hersteller angeboten. Er unterstützt den gesamten Maschinenbefehlssatz einschließlich aller Adressierungsarten.
Heutzutage sind es meistens symbolische Makro-Assembler. Sie unterstützen:
Aufteilen des Quelltextes auf mehrere Dateien
Einfügen von Kommentaren
Übersichtliche Unterteilung in Programmsegment und Datensegment
Konstante Werte direkt als Binärzahl, Oktalzahl, Dezimalzahl, Hexadezimalzahl, Zeichen oder Zeichenkette angegeben, und symbolisch benannt
Konstante Werte als zu berechnenden, arithmetischen Ausdruck angegeben
Speicheradressen symbolisch benannt
Rekursiv definierbare Makros mit Parameterübergabe
Bedingtes Assemblieren
Direktiven für die übersichtliche Behandlung besonderer Programmsituationen (Interrupt)
Programmbibliothek für häufige Programmieraufgaben
All diese Merkmale zusammen machen das Programm nicht nur deutlich lesbarer, sondern lassen die Assemblersprache fast wie eine Hochsprache erscheinen.
Programmiersprache C
C gehört zu den problemorientierten bzw. imperativen Programmiersprachen der 3. Generation. Charakteristisches Merkmal ist die algorithmische Herangehensweise an Probleme. Da die imperativen Sprachen zu den höheren Programmiersprachen gehören, müssen sie vor der Ausführung durch einen Compiler in Maschinencode umgewandelt werden.
Das zu lösende Programmierproblem wird vom Programmierer in Algorithmen (Lösungsrezepte) zerlegt. Der einzelne Algorithmus wiederum setzt sich zusammen aus Konstanten, vordefinierte und selbst definierbare abstrakte Datentypen, Variablen, Zuweisungen, Programmverzweigungen, Programmschleifen, sowie vordefinierte und selbst definierbare Funktionen.
Ein typisches C–Entwicklungssystem setzt sich zusammen aus Texteditor, C–Präprozessor, C–Compiler, Assembler und Linker.
Mit dem Texteditor wird das C–Programm erstellt. Ein C–Programm kann sich aus ein oder mehreren Quelltextdateien zusammensetzen, von denen jede einen Teil des C–Programms enthält.
Der C–Präprozessor ist ein einfacher Makroprozessor, der den Quelltext des C–Programms bearbeitet und für den C–Compiler verständlich macht. Er ermöglicht das Definieren von Konstanten und Textmakros, Aufteilung des Quelltextes auf mehrere Dateien, bedingte Kompilierung, und wird durch besondere Präprozessorbefehlszeilen gesteuert, die mit dem Zeichen '#' beginnen.
Der C–Präprozessor als separates Programm liest den vom Programmierer erstellten Quelltext ein und liefert als Ausgabe einen modifizierten, für den C–Compiler leichter verständlichen Quelltext ohne Präprozessoranweisungen.
Der C–Compiler übersetzt das C–Programm in für den Linker verständlichen Objektcode.
Der Assembler übersetzt den Prozessorspezifischen Assemblercode in für den Linker verständlichen Objektcode.
Der Linker schließlich hat die Aufgabe, die Objektmodule von C–Compiler und Assembler mit den Laufzeitbibliotheken zu binden, und so alles zu einem lauffähigen Programm zusammen zu setzen.
make
GNU-Make-Dienstprogramm zur Verwaltung von Programmgruppen
Die folgenden Programme können natürlich auch direkt aufgerufen werden.
cpp
Der C Präprozessor
gcc
GNU C und C++ Compiler
ld
Der GNU linker
strip
entfernt Symbole und andere Daten von Objekt-Dateien
9.8 Zusammenfassung der empfohlenen Ablageorte
Von Root installierte Drittanbieter-Programme:
/etc/opt | siehe /opt/etc |
/opt/<Paketname>/ | Verzeichnis für Programm-Paket. Enthaltene Programme können nach /usr/local/bin oder /usr/local/sbin verlinkt sein. |
/opt/bin | Programme für alle Benutzer. Enthaltene Programme können nach /usr/local/bin verlinkt sein. |
/opt/doc | Programm-Dokumentation |
/opt/etc | Konfigurationsdateien für Programme in /opt. /etc/opt sollte Link auf /opt/etc sein, oder umgekehrt. |
/opt/include | C Header-Dateien |
/opt/lib | Objektdateien und andere Bibliotheken |
/opt/sbin | System-Programme. Enthaltene Programme können nach /usr/local/sbin verlinkt sein. |
/opt/src | C Quelltexte |
/var/opt | Laufzeitdaten für Programme in /opt |
Programme von Root:
/etc/local | siehe /usr/local/etc |
/usr/local/bin | Programme für alle Benutzer. Darf keine Unterverzeichnisse enthalten. |
/usr/local/doc | Programm-Dokumentation |
/usr/local/etc | Konfigurationsdateien für Programme in /usr/local/bin und /usr/local/sbin. /etc/local sollte Link auf /usr/local/etc sein, oder umgekehrt. |
/usr/local/include | Header-Dateien für Compiler-Sprachen (z. B. C) |
/usr/local/lib | Objektdateien für Compiler-Sprachen (z. B. C), sowie Programmtexte die nicht direkt aufgerufen werden (z. B. bc, awk, sed, m4) |
/usr/local/sbin | System-Programme. Darf keine Unterverzeichnisse enthalten. |
/usr/local/src | Quelltexte für Compiler-Sprachen (z. B. C) |
/var/local | Laufzeitdaten für Programme in /usr/local/bin und /usr/local/sbin. |
Programme von Benutzern:
$HOME/.cache/local | Laufzeitdaten für Programme in $HOME/.local/bin. |
$HOME/.config/local | Konfigurationsdateien für Programme in $HOME/.local/bin. |
$HOME/.local/bin | Programme für lokalen Benutzer. Sollte keine Unterverzeichnisse enthalten. |
$HOME/.local/doc | Programm-Dokumentation |
$HOME/.local/include | Header-Dateien für Compiler-Sprachen (z. B. C) |
$HOME/.local/lib | Objektdateien für Compiler-Sprachen (z. B. C), sowie Programmtexte die nicht direkt aufgerufen werden (z. B. bc, awk, sed, m4) |
$HOME/.local/src | Quelltexte für Compiler-Sprachen (z. B. C) |
10. Automatisierung mit System-Dienste
10.1 Programm zeitversetzt ausführen – at
Mit dem System-Dienst at lassen sich Programme zeitversetzt ausführen.
Beispiel: Termin-Erinnerung am 5. Januar um 19:35 Uhr
echo 'echo Termin-Erinnerung' | at -t 01051935
Hier wird das auszuführende Kommando per Standard-Eingabe übergeben. Die Ausgabe erfolgt übrigends nicht auf dem Display (wie vielleicht erwartet), sondern wird per lokaler Mail versendet. Alternativ können Ausgaben auch in eine Datei umgeleitet werden.
10.2 Programm regelmäßig ausführen – cron
Mit dem System-Dienst cron lassen sich Programme regelmäßig ausführen. Programme werden genau zum angegebenen Zeitpunkt gestartet. Cron lässt sich am besten auf Rechner-Systeme einsetzen, die 24 Stunden rund um die Uhr laufen (z. B. Server).
crontab -l
listet die aktuelle Cron-Tabelle auf
crontab -e
Cron-Tabelle editieren und aktualisieren
1. Spalte: Minute (0-59)
2. Spalte: Stunde (0-23)
3. Spalte: Tag (1-31)
4. Spalte: Monat (1-12, oder jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec)
5. Spalte: Wochentag (0-7, oder sun, mon, tue, wed, thu, fri, sat), sowohl 0 als auch 7 stehen für Sonntag
6. Spalte: Kommando mit optionale Parameter
In der System-Cron-Tabelle für Root steht in der 6. Spalte der Benutzername und in der 7. Spalte Kommando mit optionalen Parameter.
*
steht für beliebigen Eintrag,
*/2
steht hier für jede zweite (Minute, oder Stunde …)
1,2,5,9
gültige Werte in einer Liste
0-4,8-12
gültige Werte als Bereichsangabe
Spezielle Zeitangaben:
@reboot
Startet Programm einmalig nach Rechner-Neustart
@yearly
Startet Programm einmal im Jahr, entspricht „0 0 1 1 *“
@annually
Startet Programm einmal im Jahr, entspricht „0 0 1 1 *“
@monthly
Startet Programm einmal im Monat, entspricht „0 0 1 * *“
@weekly
Startet Programm einmal pro Woche, entspricht „0 0 * * 0“
@daily
Startet Programm einmal am Tag, entspricht „0 0 * * *“
@hourly
Startet Programm einmal pro Stunde, entspricht „0 * * * *“
Ausgaben erfolgen übrigends auch hier nicht auf dem Display, sondern werden per lokaler Mail versendet. Alternativ können Ausgaben auch in eine Datei umgeleitet werden.
10.3 Nachrichten senden und empfangen – mail
Displayausgaben lassen sich nur dann betrachten, wenn man gerade angemeldet ist und auf das Display schaut. Bei zeitversetzten Prozessen wie bei at und cron kann man sich nicht darauf verlassen. Daher werden Standard-Ausgaben und Fehlermeldungen standardmäßig per Mail versendet. So kann der Empfänger selbst bestimmen wann er zu lesen bereit ist.
Auch das schreiben von Meldungen in eine Datei ist nicht immer die beste Lösung, insbesondere wenn mehr als ein Benutzer im System eingerichtet ist. Je nachdem welcher Benutzer das Kommando per at oder cron aufruft stimmen möglicherweise die Zugriffsrechte nicht … und resultierende Fehlermeldungen werden dann doch per Mail versendet.
Das Programm mail ist sehr komplex, hier werden nur die wichtigsten Kommandos vorgestellt.
10.3.1 Nachrichten senden
Nachrichten können ganz leicht lokal an sich selbst oder an andere eingerichtete Benutzer versendet werden.
Beispiel:
echo „Dies ist ein Test.“ | mail -s „Test-Nachricht“ anton
sendet an den Benutzer anton eine Mail mit dem Titel „Test-Nachricht“ und dem Inhalt „Dies ist ein Test.“.
10.3.2 Empfangene Nachrichten lesen
mail ruft das Programm mail interaktiv auf und zeigt die Köpfe ungelesener Nachrichten an
? t
(type) zeigt die erste Nachricht an
? d
(delete) löscht die erste Nachricht
? t
(type) zeigt die nächste Nachricht an …
? d
(delete) löscht die nächste Nachricht …
? q
(quit) verlässt das Mail-Programm