Übersicht Shell-Skripte¶
Shell-Skripte sind Programme gespeichert in Text-Dateien, die von der Shell(-Umgebung) ausgeführt werden können. Shell-Skripte werden interpretiert, nicht kompiliert. D.h. während der Ausführung liest der Interpreter das Skript Zeile für Zeile und führt diesen so aus. Ein Kompiler dagegen übersetzt ein Programm in ausführbaren Maschinencode, d.h. in eine Form, die von der Computer-Hardware direkt ausgeführt werden kann (oder von einer virtuelle Maschine, wie bei Java).
Vorteile von Shell Skripen:
- Für Shell-spezifische Aufgaben gut geeignet, bei denen gängige Komandozeilen-Tools bzw. Kommandozeilenprogramme eingesetzt werden.
Nachteile:
- Komplexe Syntax, teilweise subtile Fallstricke (wie beim Erzeugen von Subshells).
- Keine richtigen Datentypen.
Für komplexere Skripte eignen daher sich andere Skriptsprachen, wie Python, deutlich besser.
Shell-Skripe bestehen im einfachsten Fall aus einer Folge von Befehlen. Aber es sind auch Kontrollstrukturen (Schleifen, Bedingungen usw.) und Strukturierungen durch Funktionen etc. möglich.
Beachten Sie, dass es aber gute Praxis ist, den Shebang mit dem env
-Kommando zu verwenden.
Mit env
läuft ein Programm in einer modifierten Umgebung. Typischerweise liegt env
im Verzeichnis /usr/bin/
, sodass folgende Beispiele für den Shebang in der ersten Zeile eines Skriptes sinnvoll sind:
#!/usr/bin/env bash
- Bash-Skripte#!/usr/bin/env sh
- POSIX konforme Shell-Skripte#!/usr/bin/env python
- Python Skripte
env
verwendet die PATH
Umgebungsvariable (siehe unten), um den passenden Interpreter (genauer: das Interpreterprogramm) zu finden.
Beispielskript "Hallo Welt":
# Erzeugen des Skriptes und versehen mit Ausführrechten
echo '#!/usr/bin/env bash
echo Hallo Welt!' > hw.sh
chmod u+x hw.sh
./hw.sh
Hallo Welt!
Hinweis¶
Shell-Skripte haben oft die Endung .sh
. Dies ist aber optional.
Spezielle Variablen¶
Folgende spezielle Variablen gibt es in Bash bzw. Bash-Skripten
$0
Aufruf Name des Skripts (inkl. Pfad)$1
bis$9
sind Argumente des Skripts oder der Funktion (beim Aufruf übergeben).$@
sind alle Argumente als Liste (Listen/Arrays werden weiter unten behandelt)$#
Anzahl der Argumente$?
- Rückgabecode des vorherigen Kommandos$$
- Prozeß-ID (PID) des laufenden Skiptes (siehe unten)!!
- Vollständiges letztes Kommando inkl. der Argumente. Eine gängige Anwendung ist, falls das letzte Kommando aufgrund von fehlenen root-Rechten nicht ausgeführt werden konnte. So können Sie es mit dem Kürzelsudo !!
mit root-Rechten ausführen.$_
- Letztes Argument des letzten Kommandos. In einer interaktiven Shell bekommt man dies auch über die Tastenkombination Esc gefolgt von ..$!
beinhaltet die Prozess ID, des zuletzt im Hintergrund ausgeführten Prozesses.
Beispiele:
wc not-present 2>/dev/null # fehlermeldung wird unterdrückt
echo The exit code of last command is: $?
The exit code of last command is: 1
Beachten Sie, dass Fehlermeldungen von Kommandos (Programmen) mit Zahlen codiert werden. Dabei steht die Null für "Kein-Fehler":
ls >/dev/null
echo The exit code of last command is: $?
The exit code of last command is: 0
sleep 3 & # & bewirkt Ausführung im Hintergrund
echo $!
# Hier werden Prozess-IDs ausgegeben - siehe unten
[1] 10605 10605
# Hinweis: Wir müssen die $-Zeichen im here-Document escapen
# damit die bash keine Variablen-Substitution vornimmt!
cat > example_script.sh << EOF
#!/usr/bin/env bash
echo Der Name diese Skripts mit Pfad beim Aufruf: \${0}
echo Mit folgendem ersten Argument: \${1}
echo Das zweite Argument ist: \${2}
echo Die Anzahl aller Argumente ist \$#
EOF
chmod u+x example_script.sh # ausführbar machen!
# Ausführen des Skriptes
./example_script.sh foo bar
Der Name diese Skripts mit Pfad beim Aufruf: ./example_script.sh Mit folgendem ersten Argument: foo Das zweite Argument ist: bar Die Anzahl aller Argumente ist 2
cat example_script.sh
#!/usr/bin/env bash echo Der Name diese Skripts mit Pfad beim Aufruf: ${0} echo Mit folgendem ersten Argument: ${1} echo Das zweite Argument ist: ${2} echo Die Anzahl aller Argumente ist $#
Shell-Skripte¶
Im Folgenden werden kurz Funktionen, (assoziative) Arrays und Kontrollstrukturen für Shell-Skripte vorgestellt. Damit ist es möglich richtige Programme in bash zu entwickeln.
Alternativ können Sie eines der vielen Tutorials im World Wide Web mit einer Suchmaschine Ihrer Wahl finden und durcharbeiten.
Funktionen¶
Die Struktur einer Funktion ist folgende:
funktionsname () {
kommando_1
kommando_2
...
}
Für die Argumente gilt das gleiche wie bei den Skripten, d.h. es gibt $#
, $1
, etc.
zum Beispiel (Funktionen kann man auch direkt auf der Kommandozeile definieren):
# Eine sehr einfache Funktion
function get_the_meaning_of_live () {
echo 42
}
# Aufruf der Funktion mit dem Namen der Funktion:
get_the_meaning_of_live
42
# Beachte: Die Argumente werden nicht zwischen den Klammern ( ... ) explizit gelistet.
cd_ls () {
cd $1 # das erste Argument des Funktion ist (wie beim Skript) $1 usw.
echo "Das Verzeichnis $(pwd) hat folgenden Inhalt:"
ls -l
}
# cd_ls ist eine Funktion
cd_ls .
Das Verzeichnis /home/chris/HTW-nextcloud/Lehre/praktischeInformatik/bash hat folgenden Inhalt: total 10444 -rw-rw-r-- 1 chris chris 584411 Jun 23 13:29 bash_1.html -rw-r--r-- 1 chris chris 251075 Okt 24 15:02 bash_1.ipynb -rw-rw-r-- 1 chris chris 403665 Jun 23 13:29 bash_2.html -rw-r--r-- 1 chris chris 97099 Okt 24 15:02 bash_2.ipynb -rw-rw-r-- 1 chris chris 456868 Jun 23 13:29 bash_3.html -rw-r--r-- 1 chris chris 121370 Nov 3 15:52 bash_3.ipynb -rw-rw-r-- 1 chris chris 418338 Jun 23 13:29 bash_4.html -rw-r--r-- 1 chris chris 113121 Nov 1 12:33 bash_4.ipynb -rw-rw-r-- 1 chris chris 345331 Jun 23 13:29 bash_5.html -rw-r--r-- 1 chris chris 67860 Jun 30 09:06 bash_5.ipynb -rw-r--r-- 1 chris chris 3521 Mär 30 2021 bash_configuration.ipynb -rw-r--r-- 1 chris chris 43013 Mär 18 2021 Bash_Gathering_System_Information.ipynb -rw-r--r-- 1 chris chris 627 Dez 31 2020 bash-literatur.txt drwxr-xr-x 3 chris chris 4096 Dez 1 2021 bash_scripts -rw-r--r-- 1 chris chris 254 Jun 9 2020 bash.txt -rwxr--r-- 1 chris chris 668 Apr 16 2021 befehlsverzeichnis.sh~ -rw-rw-r-- 1 chris chris 23 Dez 29 2021 countries_1.txt -rw-rw-r-- 1 chris chris 80 Dez 29 2021 countries.txt -rw-rw-r-- 1 chris chris 190 Dez 29 2021 duplicate_countries -rw-r--r-- 1 chris chris 1364 Apr 16 2021 -E -rwxrw-r-- 1 chris chris 191 Nov 3 15:31 example_script.sh -rwxrw-r-- 1 chris chris 37 Nov 3 15:27 hw.sh -rw-r--r-- 1 chris chris 188465 Mär 21 2021 index.html -rw-r--r-- 1 chris chris 188465 Mär 21 2021 index.html.1 -rw-r--r-- 1 chris chris 188465 Mär 21 2021 index.html.2 -rw-r--r-- 1 chris chris 25299 Apr 9 2021 intro_bash.ipynb -rw-rw-r-- 1 chris chris 116 Dez 29 2021 logfile-example_.log -rw-rw-r-- 1 chris chris 178 Dez 29 2021 logfile-example.log -rw-r--r-- 1 chris chris 7962 Mär 19 2021 man_page_of_ls -rw-rw-r-- 1 chris chris 5319 Nov 16 2021 Minesweeper.ipynb -rw-rw-r-- 1 chris chris 6558 Nov 7 2021 minesweeper-python.ipynb -rw-rw-r-- 1 chris chris 16858 Okt 8 2021 mylog.txt -rw-r--r-- 1 chris chris 1096 Mär 19 2021 Packaging.ipynb -rw-r--r-- 1 chris chris 20274 Mär 16 2021 q -rw-rw-r-- 1 chris chris 69 Dez 29 2021 rechnernamen.txt drwxr-xr-x 3 chris chris 4096 Okt 28 13:27 regEx -rw-r--r-- 1 chris chris 120456 Mär 19 2021 Speichermedien.ipynb -rw-rw-r-- 1 chris chris 6884938 Nov 30 2021 test.ipynb -rw-r--r-- 1 chris chris 838 Apr 16 2021 weitere_aufgaben.ipynb drwxrwxrwt 8 chris chris 4096 Mai 12 13:56 work -rw-rw-r-- 1 chris chris 20 Dez 29 2021 xaa -rw-rw-r-- 1 chris chris 20 Dez 29 2021 xab -rw-rw-r-- 1 chris chris 20 Dez 29 2021 xac -rw-rw-r-- 1 chris chris 20 Dez 29 2021 xad
# Bei Funktionen kann man auch explizit das Schlüsselwort "function" angeben:
function silly_example () {
echo "Das ausführende 'Programm' bzw. Skript heißt $0"
echo "Zahl der Argumente $#"
echo "Erstes Argument $1"
}
silly_example bla blub
das ausführende 'Programm' bzw. Skript heißt /bin/bash Zahl der Argumente 2 erstes Argument bla
type function
function is a shell keyword
my_array=("first" "second" "third")
# oder bei dem Inhalte aus einer Variablen mit Leerzeichen im Inhalt
#my_array=($var)
# die Zählung beginnt bei 0
echo drittes Element: "${my_array[2]}"
echo Anzahl der Elemente "${#my_array[@]}"
drittes Element: third Anzahl der Elemente 3
# Man kann auch rückwärts indizieren:
echo "${my_array[-3]}"
first
# Alle Elemente ausgeben:
echo ${my_array[@]}
first second third
Hinzufügen eines neuen Elementes erfolgt folgendermaßen. Beachten Sie dabei die runden Klammern:
my_array+=("fourth")
echo ${my_array[@]}
first second third fourth
Alternativ kann ein leeres Indexed Array per declare -a my_array
deklariert werden.
Außerdem wird ein Index-Array automatisch erzeugt, mittels name[subscript]=value
. Hier wird subscript
als ein arithmetischer Ausdruck interpretiert, d.h. er muss eine Zahl sein oder zu ener Zahl evaluiert werden können.
Assoziative Arrays¶
Assoziative Arrays werden mittels declare -A name
erzeugt.
Mittels assoziative Arrays kann ein Schlüssel (key) auf einen Wert (value) abgebildet werden:
declare -A capital_city
capital_city["Frankreich"]="Paris"
capital_city["Spanien"]="Madrid"
capital_city["Deutschland"]="Berlin"
echo ${capital_city["Spanien"]}
echo
# all keys - note the order is undefined!
echo ${!capital_city[@]}
echo
# can be used in a loop
for k in ${!capital_city[@]}; do
echo "${capital_city[$k]} ist die Hauptstadt von ${k}."
done
Madrid Deutschland Frankreich Spanien Berlin ist die Hauptstadt von Deutschland. Paris ist die Hauptstadt von Frankreich. Madrid ist die Hauptstadt von Spanien.
Mehr zu Arrays siehe Bash Gnu Manual.
Übung¶
Schreiben Sie eine shell-Funktion denen Sie zwei Argumente übergeben:
- erste Parameter soll ein Kommentar sein, der ausgegeben (angezeigt) wird.
- der zweite Parameter soll ein Kommando sein, das ausgeführt wird.
- falls es Änderungen des Arbeitsverzeichnisses gibt (ein oder mehrere
cd
s als Kommando), soll nach Beenden der Funktion das ursprüngliche Arbeitsverzeichnis wieder gelten. Wie kann das einfach realisiert werden?
Beispiel:
comment_and_execute "Das wird bei ls -l ausgegeben:" "ls -l"
Das wird bei ls -l ausgegeben:
total 572
drwxr-.....
.....
Hinweis: Befehl eval
comment_and_execute 'Das wird bei ls -l ausgegeben:' 'ls -l'
Das wird bei ls -l ausgegeben: total 144 -rw-r--r-- 1 chris chris 79 Feb 18 09:36 countries.txt -rw-r--r-- 1 chris chris 17 Feb 12 11:09 example_scipt.sh -rwxr--r-- 1 chris chris 222 Apr 12 15:47 example_script.sh -rw-r--r-- 1 chris chris 5 Feb 5 12:33 f2.txt -rw-r--r-- 1 chris chris 11 Apr 9 11:00 File -rw-r--r-- 1 chris chris 5 Feb 5 12:50 f.txt -rw-r--r-- 1 chris chris 34 Apr 12 12:18 greetings -rw-r--r-- 1 chris chris 116 Mär 7 08:31 grep -rw-r--r-- 1 chris chris 47 Feb 8 13:10 gruesse -rwxr--r-- 1 chris chris 37 Apr 12 15:05 hw.sh -rwxr--r-- 1 chris chris 86 Feb 18 09:38 IFS_demo -rwxr--r-- 1 chris chris 86 Feb 18 09:38 IFS_demo~ -rw-r--r-- 1 chris chris 224 Mär 11 09:31 inhalt_des_Verzeichnisses -rw-r--r-- 1 chris chris 0 Apr 6 16:11 longlist -rw-r--r-- 1 chris chris 462 Mär 18 08:54 ls-out.txt drwxr-xr-x 2 chris chris 4096 Feb 6 16:37 myDir drwxr-xr-x 3 chris chris 4096 Feb 5 12:51 mydir1 -rw-r--r-- 1 chris chris 0 Apr 6 16:11 mylog.txt -rwxr--r-- 1 chris chris 58 Mär 30 12:26 mytrivialscript.sh -rw------- 1 chris chris 0 Mär 17 13:03 nohup.out -rwxr--r-- 1 chris chris 2324 Mär 30 14:20 PDFWender.sh -rwxr--r-- 1 chris chris 2326 Mär 30 14:17 PDFWender.sh~ -rw-r--r-- 1 chris chris 2 Feb 7 13:33 result-file -rwxr--r-- 1 chris chris 308 Mär 16 16:57 signal-handler.py -rwxr--r-- 1 chris chris 247 Mär 16 16:48 signal-handler.py~ -rw-r--r-- 1 chris chris 34371 Feb 8 09:53 st drwxr-xr-x 2 chris chris 4096 Feb 9 09:40 test1 -rwxr--r-- 1 chris chris 7 Feb 18 09:16 test_script.sh -rw-r--r-- 1 chris chris 116 Mär 7 08:31 TODO -rw-r--r-- 1 chris chris 7962 Feb 6 17:33 t.txt
comment_and_execute 'Das wird bei cd ~ und ls ausgegeben' 'cd ~ ; ls'
Das wird bei cd ~ und ls ausgegeben backup Downloads lehre-extern Public bin encrypted models python-virtal-envs computational-graph.pdf examples.desktop Music snap data git-repos notebooks Templates dead.letter go Pictures tmp deep.TEACHING HTW-nextcloud private Videos Desktop HTW-sensibel programs virtual-machines
Aufgabe (optional)¶
Schreiben Sie eine shell-Funktion, der Sie zwei Argumente übergeben:
- erste Parameter soll eine Liste mit Variablennamen sein.
- der zweite Parameter soll eine Zahl sein, die angibt von welcher Variable (Index) der Inhalt angezeigt wird:
Beispiel:
first="Hallo Welt"
second="Hello World"
third="Hola Mundo"
my_array=(first second third)
show_variable myarray 1
Hello World.
bzw.
show_variable my_array 0
Hallo Welt
Hinweis: Indirekte Expansion von Variablen.
first="Hallo Welt"
second="Hello World"
third="Hola Mundo"
my_array=(first second third)
show_variable my_array 2
show_variable my_array 1
show_variable my_array 0
Hola Mundo Hello World Hallo Welt
Kontrollstrukturen¶
Tests¶
Mit Tests kann man überprüfen, ob Variablen gesetzt oder leer sind bzw. sind Werte- und Stringvergleiche möglich. Dateien können auch überprüft werdem, z.B. ob sie vorhanden sind bzw. welchen Typen sie haben.
Die Tests werten zu Wahr (true
) oder Falsch (false
) aus.
So können damit auch Programm-Abzweigungen oder bedingte Ausführungen realisiert werden.
Logische Operatoren¶
&&
: Und-Operator (And)||
: Oder-Operator (Or)!
: Negation
Beide werden bedingt ausgewertet (Kurzschlussauswertung, short-circuit evaluation):
true || echo wird nicht geschrieben
false && echo wird nicht geschrieben
# ist das gleiche wie
! true && echo wird nicht geschrieben
true && echo wird geschrieben
false || echo wird geschrieben
wird geschrieben wird geschrieben
Beispielsweise kann mit dem shell builtin test
überprüft werden, ob Variablen gesetzt bzw. leer sind:
a='Hallo Welt'
test "$a" || echo Variable ist leer oder nicht gesetzt!
true || echo Variable ist leer oder nicht gesetzt!
Beachten Sie:
- Das Setzen mit einem nicht-leeren String entspricht
true
. - Leere und nicht-gesetzte Strings
false
Typischerweise werden für Tests aber eckige Klammern verwendet: [ ]
.
Die einfachen eckigen Klammern sind eine Referenz auf test
.
[ "$a" ] || echo Variable ist leer oder nicht gesetzt!
In Bash (nicht shell POSIX-konform) gibt es auch doppelte eckige Klammern [[
:
[[ $a ]] || echo Variable ist leer oder nicht gesetzt!
Zum Unterschied zwischen [
und [[
siehe http://mywiki.wooledge.org/BashFAQ/031
type [
type [[
[ is a shell builtin [[ is a shell keyword
rm /etc/some_file.conf 2>/dev/null || echo "I couldn't remove the file"
I couldn't remove the file
- Mit der Option
-z
kann überprüft werden, ob die Variable leer/nicht-gesetzt ist. Liefert im Fall leer/nicht-gesetzttrue
zurück. - Mit der Option
-n
kann überprüft werden, ob die Variable nicht-leer/gesetzt ist. Liefert im Fall nicht-leer/gesetzttrue
zurück.
Für eine Liste der Vergleichsoperatoren siehe man test
oder z.B. https://tldp.org/LDP/abs/html/comparison-ops.html
a="a"
unset a
# note: the test is true! "And"-Chain
[[ -z $a ]] && echo Variable ist leer oder nicht gesetzt!
# dasgleiche wie
[[ ! $a ]] && echo Variable ist leer oder nicht gesetzt!
# note: the test is false! "Or"-Chain
[[ -n $a ]] || echo Variable ist leer oder nicht gesetzt!
# dasgleiche wie
[[ $a ]] || echo Variable ist leer oder nicht gesetzt!
Variable ist leer oder nicht gesetzt! Variable ist leer oder nicht gesetzt! Variable ist leer oder nicht gesetzt! Variable ist leer oder nicht gesetzt!
Zur Unterscheidung von leer vs. ungesetzt können Sie folgenden Code-Snipsel verwenden (aus https://serverfault.com/questions/7503/how-to-determine-if-a-bash-variable-is-empty)
a=""
# unset a
if [[ -z ${a+x} ]]; then
echo Variable nicht gesetzt
else
echo "Variable gesetzt (kann auch leer sein)"
fi
Variable gesetzt (kann auch leer sein)
String Vergleiche¶
b="$a"
#b="Hola Mundo"
# = ist hier ein Vergleich
[[ $a = $b ]] && echo beide Variablen sind gleich.
# es geht aber auch ==
[[ $a == $b ]] && echo beide Variablen sind gleich.
[[ $a != $b ]] && echo beide Variablen sind ungleich.
beide Variablen sind gleich. beide Variablen sind gleich.
Integer Vergleiche¶
Vergleiche von Variablen, die Ganzzahlen enthalten, werden folgendermaßen vorgenommen:
# Beachten Sie die Ausgabe.
a=3
b=5
[[ $a -eq $b ]] && echo a gleich b
[[ $a -ne $b ]] && echo a ungleich b
[[ $a -gt $b ]] && echo a größer b
[[ $a -lt $b ]] && echo a kleiner b
[[ $a -ge $b ]] && echo a größer-gleich b
[[ $a -le $b ]] && echo a kleiner-gleich b
# Es geht aber auch mit einer arithmetischen Umgebung (siehe unten)
[[ (($a < $b )) ]] && echo a kleiner b
[[ (($a > $b )) ]] && echo a größer b
a ungleich b a kleiner b a kleiner-gleich b a kleiner b
Für weitere Vergleichsoperatoren für Variablen siehe https://tldp.org/LDP/abs/html/comparison-ops.html
Dateivergleiche¶
Da man die Shell oft nutzt, um Dateien zu maniplieren, existieren etliche Vergeleiche für Dateien, wie z.B.
-e
Datei existiert-f
ist eine reguläre Datei-s
Datei hat nicht Größe Null-d
Datei ist ein Verzeichnis
[[ -e "./countries.txt" ]] && echo "countries.txt existiert im Arbeitsverzeichnis"
[[ -d "." ]] && echo "Das Arbeitsverzeichnis '.' ist wenig überraschend ein Verzeichnis."
countries.txt existiert im Arbeitsverzeichnis Das Arbeitsverzeichnis '.' ist wenig überraschend ein Verzeichnis.
Für weitere Vergleichsoperatoren für Dateien siehe https://tldp.org/LDP/abs/html/fto.html
Übung¶
Erklären Sie den Fehler des folgenden Code-Schnipsels und beseitigen Sie die Fehlermeldung (aber richtiger Vergleich):
meinName='Christian Herta'
deinName='Jemand anderes'
[ $meinName = $deinName ] || echo zwei unterschiedliche Personen
bash: [: too many arguments zwei unterschiedliche Personen
If, then, else¶
# example from https://program-script.com/bash/control-structures/if-then-else.php
function if_then_else(){
number=$1
if [ $number -gt 0 ]; then
echo "Zahl ist positiv."
elif [ $number -lt 0 ]; then
echo "Zahl ist negativ."
else
echo "Zahl ist Null."
fi
}
if_then_else -1
Zahl ist negativ.
Rückgabewerte als Boolsche Werte¶
Die Rückgabewerte von Kommandos (Programmen) werden oft in bash-Skripten etc. als boolesche Werte interpretiert. Wenn ein Programm keinen Fehler zurückgibt, ist der Returnwert 0.
Beispielsweise wird in C-Programmen die main
-Funktion in diesem Fall mit return 0
beendet.
ls * 1>/dev/null; echo $?
ls this-file-does-not-exits 2>/dev/null; echo $?
0 2
Vergleichen Sie dies mit:
ls * 1>/dev/null && echo "Das Kommando war erfolgreich (exit code = 0)"
ls this-file-does-not-exits 2>/dev/null || echo "Das Kommando war nicht erfolgreich (exit code != 0)"
Das Kommando war erfolgreich (exit code = 0) Das Kommando war nicht erfolgreich (exit code != 0)
Das bedeutet true
ist 0 und false
ist 1
im Gegensatz zu den meisten Programmiersprachen!
true; echo true entspricht $? # das letzte "Kommando" war `true`
false; echo false entspricht $? # das letzte "Kommando" war `false`
true entspricht 0 false entspricht 1
if who | grep chris > /dev/null
then
echo exit code ist $?. Das ist auch der boolesche Wert für true.
echo 'Chris ist eingeloggt.'
else
echo exit code ist ${?}.Das ist auch der boolesche Wert für false.
echo 'Chris ist nicht eingeloggt.'
fi
exit code ist 0. Das ist auch der boolesche Wert für true. Chris ist eingeloggt.
Regular Expression Tests¶
# simple regEx
VAR="Tesus"
if [[ $VAR =~ Th?esus ]] # =~ regex-check
then
echo match
fi
match
# grouping:
VAR="IP address is 192.168.0.1"
regEx="[[:alpha:]]*([[:digit:]]+)\.([[:digit:]]+)\.([[:digit:]]+)\.([[:digit:]]+)"
if [[ $VAR =~ $regEx ]] # =~ regex-check
then
j=0
for i in "${BASH_REMATCH[@]}"; do
echo '${BASH_REMATCH['$j'] =' $i # ${BASH_REMATCH[$j]}
((j++)) # Arithmetic Operationen siehe unten
done
fi
${BASH_REMATCH[0] = 192.168.0.1 ${BASH_REMATCH[1] = 192 ${BASH_REMATCH[2] = 168 ${BASH_REMATCH[3] = 0 ${BASH_REMATCH[4] = 1
Aufgabe¶
Sehen Sie sich die Funktionen der regEx-Übung an und analysieren Sie diese.
Arithmetische Operationen¶
Es verschiedene Möglichkeiten arithmetische Berechnungen durchzuführen, wie expr
, let
, declare
und arithmetic Expansions. Dabei ist letzteres für einfache ganzzahlige Operatoren zu bevorzugen:
$((...))
Arithmetische Expansion mit eventueller Änderung der Shell-Variablen mit Rückgabewert.((...))
Compound Command für arithmetische Ausdrücke (arithmetic expressions), d.h. arithmetische Berechnungen mit eventueller Änderung der Shell-Variablen (ohne Rückgabewert).
echo 3 + 4 ist $((3+4))
3 + 4 ist 7
a=5
((a++)) # Beachte: kein $ vor der Variablen a
echo $a
((a*=2))
echo $a
6 12
((a=4+3)); echo $a
echo $((a=4+4)) ist $a
7 8 ist 8
Kompliziertere mathematische Operationen können z.B. mit bc
(math-library mit -l
) ausgeführt werden
(siehe auch man bc
):
whatis bc
echo
a=5.5 # auch Fließkommazahlen
x=$(echo "e(.6+$a)" | bc -l)
echo "$x"
bc (1) - An arbitrary precision calculator language bc (1posix) - arbitrary-precision arithmetic language 445.85777008251693179233
Übung¶
Erklären Sie folgenden Codeschnipsel in Hinsicht auf die booleschen Werte:
number=1
[ $number -gt 0 ]; echo $?
[ $number -eq 0 ]; echo $?
[ $number -lt 0 ]; echo $?
echo ------------------
((0)); echo $?
((1)); echo $?
((number==1)); echo $?
((number==2)); echo $?
0 1 1 ------------------ 1 0 0 1
a=4
$(( a++ )) # Ziel erhöhe a um Eins
echo $a
4: command not found 5
a=4
(( a++ )) # Ziel erhöhe a um Eins
echo $a
5
Schleifen¶
While-Loop¶
z=1
while [[ $z -le 5 ]]
do
echo $z
((z++))
done
1 2 3 4 5
Until-Loop¶
z=1
until [ $z -gt 5 ]
do
echo $z
((z++))
done
1 2 3 4 5
Break und Continue¶
break
- Loop verlassencontinue
- Beende laufende Iteration
# ein ziemlich überkompliziertes Beispiel
# in sehr schlechtem Stil
z=0
while true
do
[[ $z -eq 10 ]] && break
((z++))
if (($z % 2)); then
continue # das ist nur zur Demonstration von continue und so nicht sinnvoll!
fi
echo "$z"
done
2 4 6 8 10
Foreach-Loops¶
Beispiel mit den Aufruf-Argumenten eines Skriptes
echo '#!/usr/bin/env bash
echo das script $0 wurde mit $# Argumenten aufgerufen.
for i in $@ # Loop over all arguments
do
echo $i
done
' > example_script.sh
./example_script.sh # Loop over all arguments
das script ./example_script.sh wurde mit 0 Argumenten aufgerufen.
C-Stye For-Loop¶
# erinnerung (( ... )) für arithmetiche Ausdrücke
for ((c=1; c<=5; c++)); do
echo "Welcome $c times"
done
Welcome 1 times Welcome 2 times Welcome 3 times Welcome 4 times Welcome 5 times
Beachten Sie: hier wurde das do
in dieselbe Zeile des for
gesetzt. Dafür muss
ein Semikolon als Trenner verwendet werden.
Range für For-Loop¶
Man kann auch alles in einer Zeile schreiben, wie hier:
for c in {1..6} ; do echo "Welcome $c times"; done
Welcome 1 times Welcome 2 times Welcome 3 times Welcome 4 times Welcome 5 times Welcome 6 times
# Erinnerung: Brace Expansion
# Range
echo {1..6}
# geht auch rückwärts mit anderer Schrittweite
echo {10..0..2}
1 2 3 4 5 6 10 8 6 4 2 0
Hinweis: Mit select
für einen Loop lässt sich ein einfaches Eingabe-Menü realisieren, siehe
https://ryanstutorials.net/bash-scripting-tutorial/bash-loops.php#select
Loop über Dateien in Verzeichnis¶
Mittels bash Skripten kann man auch einfach Dateien in Verzeichnissen für die weitere Verarbeitung iterieren.
for file in ./*.txt
do
echo $file
done
./countries.txt ./f2.txt ./f.txt ./mylog.txt ./t.txt
Beachte Sie, dass das Globbing ./*.txt
von der Shell aufgelöst wird, sodass
effektiv dort ein Liste von Strings steht. So ähnlich, wie hier gezeigt:
for file in a.txt b.txt c.txt
do
echo $file
done
a.txt b.txt c.txt
Falls das Globbing nicht ausgelöst wird, d.h. keine Dateien auf das Glob-Muster zutreffen, wird das Glob-Muster direkt verwendet. Dies kann zu Fehlermeldungen führen, wie hier demonstriert:
for file in ./*.texte
do
ls $file
done
ls: cannot access './*.texte': No such file or directory
In der Bash kann das Verhalten des Globbings mit dem Befehl shopt
geändert werden.
shopt
kann das Verhalten der Shell geändert werden (und somit auch des Globbings),
mehr siehe https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html.
Mit shopt -s nullglob
wird ein Globmuster leer ersetzt, falls es auf nichts zutrifft.
shopt -s nullglob # set (-s) von nullglob
for file in ./*.texte
do
ls "$file"
done
Hier setzen wird das Standardverhalten wieder zurück:
# unset (-u) von nullglob
shopt -u nullglob
Mit Hilfe eines Tests (Datei existiert?) und continue
lässt sich das Problem auch beseitigen:
for file in ./*.texte; do
[ -f "$file" ] || continue
ls "$file"
done
Datei zeilenweise lesen¶
Mittels read
(siehe help read
) kann man eine Datei zeilenweise einlesen:
cat countries.txt | while read line; do
echo $line
done
France Canada Burkina Faso Democratic Republic of the Congo Russia New Zealand
Typischerweise sieht das zeilenweise Einlesen aus einer Datei in einem Skript folgendermaßen aus:
#!/bin/bash
input="/path/to/txt/file"
while IFS= read -r line
do
echo "$line"
done < "$input"
Das IFS=
bewirkt, dass der internal field separator (IFS
) auf Nichts eingestellt wird. So kann verhindert werden, dass führende und abschließende Leerzeichen nicht mitgelesen werden. D.h. man will in der Regel die führende und abschließende Leerzeichen in der Laufvariable (hier line
) dabei haben.
Aufgabe¶
Probieren Sie obiges Skript aus und löschen Sie auch mal das IFS=
weg. Erzeugen Sie hierzu eine entsprechende Textdatei mit führenden und abschließenden Leerzeichen. Ändern Sie das Skript so ab, dass die Pfad zur Textdatei als Argument übergeben wird.
Case-Statement¶
Beispiel:
cat >case-example.sh << EOF
#!/usr/bin/env bash
country=\$1
case \$country in
Marokko | Somalia)
echo "\$country liegt in Afrika."
;;
Brasilien | Peru)
echo "\$country liegt in Südamerika."
;;
*)
echo "\$country kenne ich nicht."
;;
esac
EOF
chmod u+x case-example.sh
./case-example.sh "Brasilien"
./case-example.sh "Kuba"
Brasilien liegt in Südamerika Kuba kenne ich nicht.
Skript Schalter¶
Mittels der Tools getopt
, getopts
oder getoption
lassen sich Schalter (als Argumente) für Skripte in Kurzform, wie z.B. -f
, einfach auswerten. Beispiel getopts
:
#!/bin/bash
OPTIND=1 # Reset in case getopts has been used previously in the shell.
# Initialize our own variables:
verbose=0
function show_help {
echo "Usage: ${0} [options]"
echo "Options:"
echo " -t"
echo " test run to check the configuration."
echo " -d"
echo " distributed training."
echo " -c"
echo " path to configuration file (should not be used together with -d)."
exit
}
# "h?vtdc:" are the possible arguments
# "c:" means that the switch "-c" is followed by an argument
while getopts "h?vtdc:" opt; do
case "$opt" in
h|\?)
show_help
exit 0
;;
v) verbose=1
;;
t) test='--test'
;;
d) distributed=1
;;
c) config=$OPTARG
esac
done
Das Skript kann z.B. mittels ./skript.sh -t -c myconfig.txt
aufgerufen werden.
Für ein ausführlicheres getopts
-Tutorial, siehe z.B. https://wiki.bash-hackers.org/howto/getopts_tutorial
Für weitere Möglichkeiten (neben getopts
) siehe z.B.
https://stackoverflow.com/questions/192249/how-do-i-parse-command-line-arguments-in-bash
Bash Strict-Mode¶
Hilfreich ist es für Skripte den Strict-Mode einzuschalten. Dazu beginnt man ein bash-Skript mit folgenden Zeilen:
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
Dies vermeidet subtile Fehler zur Laufzeit. Lesen Sie hierzu: unofficial-bash-strict-mode
Das Setzen des IFS
erfolgt hier mit ANSI-C-Quoting
Tipp: Shellcheck¶
Es gibt Checker-Werkzeuge, wie shellcheck, die Ihnen helfen Fehler zu finden.
Tipp: Robustere Skripte via "Exit Traps"¶
Wenn bash-Skripte beendet werden, wird das pseudo-Signal EXIT
gesendet. Dieses kann mit Hilfe des Kommandos trap
abgefangen werden und z.B. eine Funktion aufgerufen werden, die Resourcen freigibt, z.B. im Skript erzeugte temporäre Dateien löscht oder Locks löst.
Mehr hierzu siehe http://redsymbol.net/articles/bash-exit-traps/
Übungen: Skripte¶
Aufgabe¶
Schreiben Sie ein Skript, das alle MP3 Dateien in einem Ordner mit einer Zahl am Anfang, so umbenennt, dass die Zahl immer 3 Stellen hat. Hintergrund: Ein MP3-Player spielt die Dateien meist nach String-Sortierung ab.
- z.B. wird so aus
1_Einleitung.mp3
die Datei001_Einleitung.mp3
- z.B. wird so aus
14_Das_Erwachen.mp3
die Datei014_Das_Erwachen.mp3
Erzeugen Sie zum Testen Ihres Skriptes mit touch
und brace expansion hundert Dateien, mit dem passenden Muster.
Lernkontrollfragen¶
Folgende Fragen sollten Sie ohne Nachschlagen beantworten können:
- Was sind Textdateien? Was ist ASCII, Unicode und UTF-8?
- Was ist der Shebang? Wofür wird dieser verwendet?
- Was ist eine Umgebungsvariable?
- Wie findet die Shell ausführbare Dateien? Welche Umgebungsvariable wird hier verwendet?
- Wann benötigt man
export
beim Setzen von Variablen? - Wozu dienen die Dateien
.bashrc
bzw..bash_profile
? Was bedeutet der Punkt am Anfang des Dateinamens? - Was ist der Alias-Mechanismus?
- Was sind Indexed Arrays und Assoziative Arrays?
Die spezielle Skript-Syntax können Sie nachschlagen. Diese brauchen Sie nicht auswendiglernen. Entwickeln Sie stattdessen einige Beispielskripte, z.B. um Aufgaben zu automatisieren.