Bash Scripting
Inhaltsverzeichnis
- Grundlagen
- Shebang und Interpreter
- Variablen
- Benutzereingaben
- Bedingungen (if/else)
- Schleifen (for/while/until)
- Funktionen
- Arrays
- String-Manipulation
- Mathematische Operationen
- Dateien und Verzeichnisse
- Input/Output und Redirection
- Error Handling
- Reguläre Ausdrücke
- Netzwerk und Ports
- Prozesse und Jobs
- Debugging
- Best Practices
- Praktische Beispiele
1. Grundlagen
Was ist ein Bash-Script?
Ein Bash-Script ist eine Textdatei mit Befehlen, die von der Bash-Shell ausgeführt werden. Scripts automatisieren wiederkehrende Aufgaben und kombinieren mehrere Befehle.
Warum Bash-Scripts?
- Automatisierung: Wiederkehrende Aufgaben automatisch ausführen
- Systemverwaltung: Server-Management, Backups, Monitoring
- Kombinieren von Tools: Unix-Tools miteinander verknüpfen
- Portabilität: Läuft auf fast allen Unix/Linux-Systemen
Erstes Script erstellen
1. Datei erstellen:
nano mein-script.sh
2. Inhalt schreiben:
#!/bin/bash
echo "Hallo Welt!"
3. Ausführbar machen:
chmod +x mein-script.sh
4. Ausführen:
./mein-script.sh
Ausführungsmethoden
# Methode 1: Direkt (benötigt chmod +x)
./script.sh
# Methode 2: Mit Interpreter
bash script.sh
# Methode 3: Source (läuft im aktuellen Shell-Kontext)
source script.sh
# oder
. script.sh
Unterschied source vs. ./:
# ./script.sh
# - Läuft in Sub-Shell
# - Variablen sind nicht im Parent verfügbar
# - Exit beendet nur das Script
# source script.sh
# - Läuft in aktueller Shell
# - Variablen bleiben verfügbar
# - Exit beendet die ganze Shell!
2. Shebang und Interpreter
Was ist der Shebang?
Die erste Zeile eines Scripts, die angibt, welcher Interpreter verwendet werden soll.
#!/bin/bash
Verschiedene Shebangs
#!/bin/bash
# Standard Bash
#!/bin/sh
# POSIX-kompatible Shell (meist dash auf Ubuntu)
#!/usr/bin/env bash
# Bash über PATH finden (portabler)
#!/usr/bin/python3
# Python-Script
#!/usr/bin/perl
# Perl-Script
Wann welchen Shebang?
Verwende #!/bin/bash wenn:
- Du Bash-spezifische Features nutzt (Arrays, [[...]], etc.)
- Das Script nur auf Systemen mit Bash läuft
Verwende #!/bin/sh wenn:
- Maximale Portabilität gewünscht
- Nur POSIX-Features verwendet werden
- Script auf embedded Systems laufen soll
Verwende #!/usr/bin/env bash wenn:
- Bash an verschiedenen Orten sein könnte
- Maximale Portabilität bei Bash-Scripts
- Empfohlen für moderne Scripts
Interpreter-Optionen
#!/bin/bash -x
# Debug-Modus (zeigt jeden Befehl)
#!/bin/bash -e
# Exit bei Fehler
#!/bin/bash -u
# Exit bei undefined Variablen
#!/bin/bash -euo pipefail
# Kombination (sehr strikt, empfohlen!)
3. Variablen
Variablen definieren
# Einfache Zuweisung (KEINE Leerzeichen um =!)
NAME="Max"
ALTER=25
DATEI="/tmp/test.txt"
# Ausgabe
echo $NAME
echo ${NAME} # Besser, eindeutiger
Wichtig: Keine Leerzeichen um =!
# FALSCH:
NAME = "Max"
# RICHTIG:
NAME="Max"
Variablen verwenden
NAME="Max"
# Standard
echo $NAME
# Mit Klammern (empfohlen)
echo ${NAME}
# In Strings
echo "Hallo $NAME"
echo "Hallo ${NAME}!"
# Ohne Interpolation (single quotes)
echo 'Hallo $NAME' # Ausgabe: Hallo $NAME
Spezielle Variablen
$0 # Name des Scripts
$1-$9 # Erste 9 Parameter
${10} # Ab 10. Parameter mit Klammern
$# # Anzahl Parameter
$@ # Alle Parameter als separate Wörter
$* # Alle Parameter als ein String
$? # Exit-Code des letzten Befehls
$$ # PID des Scripts
$! # PID des letzten Background-Prozesses
$_ # Letztes Argument des vorherigen Befehls
Beispiel:
#!/bin/bash
echo "Script-Name: $0"
echo "Erster Parameter: $1"
echo "Zweiter Parameter: $2"
echo "Anzahl Parameter: $#"
echo "Alle Parameter: $@"
echo "Script-PID: $$"
Aufruf:
./script.sh hallo welt
# Ausgabe:
# Script-Name: ./script.sh
# Erster Parameter: hallo
# Zweiter Parameter: welt
# Anzahl Parameter: 2
# Alle Parameter: hallo welt
# Script-PID: 12345
Umgebungsvariablen
# Wichtige System-Variablen
echo $HOME # Home-Verzeichnis
echo $USER # Aktueller User
echo $PATH # Executable-Pfade
echo $PWD # Aktuelles Verzeichnis
echo $OLDPWD # Vorheriges Verzeichnis
echo $SHELL # Aktuelle Shell
echo $HOSTNAME # Hostname
# Eigene exportieren (für Child-Prozesse verfügbar)
export MEINE_VAR="Wert"
# Nur im aktuellen Script
LOKALE_VAR="Wert"
Command Substitution
Befehlsausgabe in Variable speichern:
# Moderne Syntax (empfohlen)
DATUM=$(date)
DATEIEN=$(ls -la)
ZEILEN=$(wc -l < datei.txt)
# Alte Syntax (veraltet)
DATUM=`date`
# Beispiel
ANZAHL=$(ls | wc -l)
echo "Es gibt $ANZAHL Dateien"
Variable mit Default-Wert
# Wenn Variable leer/unset, Default verwenden
echo ${VAR:-"Standard"}
# Wenn Variable leer/unset, Default setzen UND verwenden
echo ${VAR:="Standard"}
# Wenn Variable leer/unset, Fehler ausgeben
echo ${VAR:?"Variable VAR ist nicht gesetzt!"}
# Wenn Variable gesetzt, alternativen Wert verwenden
echo ${VAR:+"Alternativer Wert"}
Beispiel:
#!/bin/bash
# Port von Parameter oder Default 8080
PORT=${1:-8080}
echo "Verwende Port: $PORT"
# Aufruf ohne Parameter:
./script.sh
# Ausgabe: Verwende Port: 8080
# Aufruf mit Parameter:
./script.sh 3000
# Ausgabe: Verwende Port: 3000
Readonly und unset
# Variable als read-only deklarieren
readonly PI=3.14159
PI=3.14 # Fehler!
# Variable löschen
VAR="Test"
unset VAR
echo $VAR # Leer
4. Benutzereingaben
read - Einfache Eingabe
#!/bin/bash
echo "Wie heißt du?"
read NAME
echo "Hallo $NAME!"
read mit Prompt
#!/bin/bash
read -p "Gib deinen Namen ein: " NAME
echo "Hallo $NAME!"
read ohne Echo (Passwort)
#!/bin/bash
read -sp "Passwort: " PASSWORD
echo "" # Neue Zeile
echo "Passwort gesetzt: ${#PASSWORD} Zeichen"
read mit Timeout
#!/bin/bash
if read -t 5 -p "Eingabe (5 Sekunden): " INPUT; then
echo "Du hast eingegeben: $INPUT"
else
echo "Timeout!"
fi
read mit Default-Wert
#!/bin/bash
read -p "Port [8080]: " PORT
PORT=${PORT:-8080}
echo "Verwende Port: $PORT"
Mehrere Variablen gleichzeitig
#!/bin/bash
read -p "Vorname Nachname: " VORNAME NACHNAME
echo "Vorname: $VORNAME"
echo "Nachname: $NACHNAME"
read in Array
#!/bin/bash
echo "Gib mehrere Werte ein (Leerzeichen-getrennt):"
read -a ARRAY
echo "Erstes Element: ${ARRAY[0]}"
echo "Alle Elemente: ${ARRAY[@]}"
Zeile für Zeile einlesen
#!/bin/bash
while read -r ZEILE; do
echo "Gelesen: $ZEILE"
done < datei.txt
Yes/No Abfrage
#!/bin/bash
read -p "Fortfahren? (j/n): " ANTWORT
if [[ "$ANTWORT" =~ ^[Jj]$ ]]; then
echo "Weiter geht's!"
else
echo "Abgebrochen"
exit 1
fi
5. Bedingungen (if/else)
Grundstruktur
if [ BEDINGUNG ]; then
# Code wenn wahr
fi
if [ BEDINGUNG ]; then
# Code wenn wahr
else
# Code wenn falsch
fi
if [ BEDINGUNG1 ]; then
# Code wenn BEDINGUNG1 wahr
elif [ BEDINGUNG2 ]; then
# Code wenn BEDINGUNG2 wahr
else
# Code wenn alle falsch
fi
[ ] vs [[ ]]
Verwende [[ ]] (empfohlen in Bash):
# Moderne Bash-Syntax
if [[ $VAR == "test" ]]; then
echo "Gleich"
fi
# Vorteile:
# - Pattern Matching
# - Keine Quoting-Probleme
# - && und || direkt nutzbar
Verwende [ ] (POSIX-kompatibel):
# Alte/portable Syntax
if [ "$VAR" = "test" ]; then
echo "Gleich"
fi
# Nachteile:
# - Variablen müssen gequoted werden
# - Nur -a und -o für AND/OR
String-Vergleiche
# Gleichheit
if [[ "$STR1" == "$STR2" ]]; then
echo "Gleich"
fi
# Ungleichheit
if [[ "$STR1" != "$STR2" ]]; then
echo "Nicht gleich"
fi
# Leer?
if [[ -z "$STR" ]]; then
echo "String ist leer"
fi
# Nicht leer?
if [[ -n "$STR" ]]; then
echo "String ist nicht leer"
fi
# Pattern Matching (nur mit [[ ]])
if [[ "$DATEI" == *.txt ]]; then
echo "Ist eine TXT-Datei"
fi
# Regex (nur mit [[ ]])
if [[ "$EMAIL" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
echo "Gültige E-Mail"
fi
Zahlen-Vergleiche
# Gleich
if [[ $NUM1 -eq $NUM2 ]]; then
echo "Gleich"
fi
# Ungleich
if [[ $NUM1 -ne $NUM2 ]]; then
echo "Ungleich"
fi
# Größer
if [[ $NUM1 -gt $NUM2 ]]; then
echo "Größer"
fi
# Größer oder gleich
if [[ $NUM1 -ge $NUM2 ]]; then
echo "Größer oder gleich"
fi
# Kleiner
if [[ $NUM1 -lt $NUM2 ]]; then
echo "Kleiner"
fi
# Kleiner oder gleich
if [[ $NUM1 -le $NUM2 ]]; then
echo "Kleiner oder gleich"
fi
Übersicht Zahlen-Operatoren:
-eq: equal (gleich)-ne: not equal (ungleich)-gt: greater than (größer)-ge: greater or equal (größer oder gleich)-lt: less than (kleiner)-le: less or equal (kleiner oder gleich)
Datei-Tests
# Datei existiert?
if [[ -e "$DATEI" ]]; then
echo "Existiert"
fi
# Ist eine normale Datei?
if [[ -f "$DATEI" ]]; then
echo "Ist eine Datei"
fi
# Ist ein Verzeichnis?
if [[ -d "$VERZEICHNIS" ]]; then
echo "Ist ein Verzeichnis"
fi
# Ist lesbar?
if [[ -r "$DATEI" ]]; then
echo "Ist lesbar"
fi
# Ist schreibbar?
if [[ -w "$DATEI" ]]; then
echo "Ist schreibbar"
fi
# Ist ausführbar?
if [[ -x "$DATEI" ]]; then
echo "Ist ausführbar"
fi
# Ist ein Symlink?
if [[ -L "$DATEI" ]]; then
echo "Ist ein Symlink"
fi
# Datei ist nicht leer?
if [[ -s "$DATEI" ]]; then
echo "Datei ist nicht leer"
fi
# Datei1 ist neuer als Datei2?
if [[ "$DATEI1" -nt "$DATEI2" ]]; then
echo "Datei1 ist neuer"
fi
# Datei1 ist älter als Datei2?
if [[ "$DATEI1" -ot "$DATEI2" ]]; then
echo "Datei1 ist älter"
fi
Logische Operatoren
# UND (beide müssen wahr sein)
if [[ $AGE -ge 18 ]] && [[ $LAND == "DE" ]]; then
echo "Erwachsen und in Deutschland"
fi
# Oder innerhalb [[ ]]
if [[ $AGE -ge 18 && $LAND == "DE" ]]; then
echo "Erwachsen und in Deutschland"
fi
# ODER (eines muss wahr sein)
if [[ $TAG == "Samstag" ]] || [[ $TAG == "Sonntag" ]]; then
echo "Wochenende!"
fi
# Oder innerhalb [[ ]]
if [[ $TAG == "Samstag" || $TAG == "Sonntag" ]]; then
echo "Wochenende!"
fi
# NICHT (Negation)
if [[ ! -f "$DATEI" ]]; then
echo "Datei existiert nicht"
fi
Case-Statement
Alternative zu vielen if/elif für Pattern-Matching:
#!/bin/bash
read -p "Wähle eine Option (a/b/c): " OPTION
case $OPTION in
a)
echo "Option A gewählt"
;;
b)
echo "Option B gewählt"
;;
c)
echo "Option C gewählt"
;;
*)
echo "Ungültige Option"
;;
esac
Mit Patterns:
#!/bin/bash
DATEI="$1"
case $DATEI in
*.txt)
echo "Text-Datei"
;;
*.jpg|*.png|*.gif)
echo "Bild-Datei"
;;
*.sh)
echo "Shell-Script"
;;
*)
echo "Unbekannter Typ"
;;
esac
Mit Ranges:
#!/bin/bash
read -p "Gib eine Zahl (1-100): " ZAHL
case $ZAHL in
[1-9]|[1-9][0-9])
echo "Gültige Zahl: $ZAHL"
;;
100)
echo "Maximum erreicht!"
;;
*)
echo "Ungültig"
;;
esac
6. Schleifen (for/while/until)
for-Schleife - über Liste
#!/bin/bash
# Über Wörter iterieren
for NAME in Max Lisa Tom; do
echo "Hallo $NAME"
done
# Über Zahlenbereich (Bash 4+)
for i in {1..10}; do
echo "Zahl: $i"
done
# Mit Schrittweite
for i in {0..100..10}; do
echo "Zahl: $i"
done
# Über Dateien
for DATEI in *.txt; do
echo "Verarbeite: $DATEI"
done
# Über Verzeichnisse
for DIR in */; do
echo "Verzeichnis: $DIR"
done
for-Schleife - C-Style
#!/bin/bash
for ((i=0; i<10; i++)); do
echo "Iteration $i"
done
# Mit mehreren Variablen
for ((i=0, j=10; i<10; i++, j--)); do
echo "i=$i, j=$j"
done
for-Schleife - über Array
#!/bin/bash
NAMEN=("Max" "Lisa" "Tom")
for NAME in "${NAMEN[@]}"; do
echo "Hallo $NAME"
done
# Mit Index
for i in "${!NAMEN[@]}"; do
echo "Index $i: ${NAMEN[$i]}"
done
for-Schleife - über Command-Output
#!/bin/bash
# Über Zeilen
for ZEILE in $(cat datei.txt); do
echo "$ZEILE"
done
# Besser: while read verwenden (siehe unten)
# Über Prozesse
for PID in $(pgrep firefox); do
echo "Firefox PID: $PID"
done
while-Schleife
#!/bin/bash
# Einfaches Beispiel
i=0
while [[ $i -lt 10 ]]; do
echo "i = $i"
((i++))
done
# Endlosschleife
while true; do
echo "Drücke Ctrl+C zum Beenden"
sleep 1
done
# Bedingung prüfen
while [[ -f "/tmp/lock" ]]; do
echo "Warte auf Lock-Datei..."
sleep 5
done
while read - Datei zeilenweise lesen
#!/bin/bash
# Beste Methode zum Datei-Lesen
while read -r ZEILE; do
echo "Gelesen: $ZEILE"
done < datei.txt
# Mit IFS (Input Field Separator) für CSV
while IFS=',' read -r NAME ALTER STADT; do
echo "Name: $NAME, Alter: $ALTER, Stadt: $STADT"
done < personen.csv
# Aus Pipe
cat datei.txt | while read -r ZEILE; do
echo "$ZEILE"
done
# ABER ACHTUNG: Variablen in Pipe-while sind nicht außerhalb verfügbar!
# Besser: Process Substitution verwenden
while read -r ZEILE; do
((COUNTER++))
done < <(cat datei.txt)
echo "Zeilen gesamt: $COUNTER" # Funktioniert!
until-Schleife
Läuft bis Bedingung WAHR wird (Gegenteil von while):
#!/bin/bash
i=0
until [[ $i -ge 10 ]]; do
echo "i = $i"
((i++))
done
# Warten bis Datei existiert
until [[ -f "/tmp/bereit" ]]; do
echo "Warte auf Datei..."
sleep 1
done
echo "Datei existiert!"
break und continue
#!/bin/bash
# break - verlässt Schleife komplett
for i in {1..10}; do
if [[ $i -eq 5 ]]; then
break
fi
echo $i
done
# Ausgabe: 1 2 3 4
# continue - überspringt Rest der Iteration
for i in {1..10}; do
if [[ $i -eq 5 ]]; then
continue
fi
echo $i
done
# Ausgabe: 1 2 3 4 6 7 8 9 10
Verschachtelte Schleifen
#!/bin/bash
# Multiplikationstabelle
for i in {1..10}; do
for j in {1..10}; do
printf "%4d" $((i * j))
done
echo ""
done
# break mit Ebenen
for i in {1..5}; do
for j in {1..5}; do
if [[ $j -eq 3 ]]; then
break 2 # Bricht beide Schleifen ab
fi
echo "i=$i, j=$j"
done
done
7. Funktionen
Funktion definieren
# Methode 1 (empfohlen)
function meine_funktion {
echo "Hallo aus der Funktion"
}
# Methode 2 (POSIX-kompatibel)
meine_funktion() {
echo "Hallo aus der Funktion"
}
# Aufrufen
meine_funktion
Funktionen mit Parametern
#!/bin/bash
gruss() {
echo "Hallo $1!"
}
gruss "Max" # Ausgabe: Hallo Max!
gruss "Lisa" # Ausgabe: Hallo Lisa!
# Mehrere Parameter
addiere() {
local SUMME=$(($1 + $2))
echo $SUMME
}
ERGEBNIS=$(addiere 5 3)
echo "5 + 3 = $ERGEBNIS"
Parameter in Funktionen
#!/bin/bash
funktion() {
echo "Funktion: $0" # Script-Name (nicht Funktionsname!)
echo "Erster Parameter: $1"
echo "Zweiter Parameter: $2"
echo "Anzahl Parameter: $#"
echo "Alle Parameter: $@"
}
funktion eins zwei drei
Return-Werte
#!/bin/bash
# return gibt Exit-Code zurück (0-255)
ist_gerade() {
if [[ $(($1 % 2)) -eq 0 ]]; then
return 0 # Wahr
else
return 1 # Falsch
fi
}
# Verwendung
if ist_gerade 4; then
echo "4 ist gerade"
fi
# Für andere Werte: echo verwenden
quadrat() {
echo $(($1 * $1))
}
ERGEBNIS=$(quadrat 5)
echo "5² = $ERGEBNIS"
Lokale Variablen
#!/bin/bash
GLOBAL="Global"
funktion() {
local LOKAL="Lokal"
GLOBAL="Geändert"
echo "In Funktion:"
echo " GLOBAL=$GLOBAL"
echo " LOKAL=$LOKAL"
}
funktion
echo "Außerhalb:"
echo " GLOBAL=$GLOBAL" # Geändert
echo " LOKAL=$LOKAL" # Leer
Wichtig: Immer local für Funktions-Variablen verwenden!
Funktionen mit Fehlerbehandlung
#!/bin/bash
datei_lesen() {
local DATEI="$1"
if [[ ! -f "$DATEI" ]]; then
echo "Fehler: Datei $DATEI existiert nicht" >&2
return 1
fi
cat "$DATEI"
return 0
}
# Verwendung
if datei_lesen "test.txt"; then
echo "Erfolgreich gelesen"
else
echo "Fehler beim Lesen"
fi
Rekursive Funktionen
#!/bin/bash
# Fakultät berechnen
fakultaet() {
if [[ $1 -le 1 ]]; then
echo 1
else
local TEMP=$(fakultaet $(($1 - 1)))
echo $(($1 * TEMP))
fi
}
ERGEBNIS=$(fakultaet 5)
echo "5! = $ERGEBNIS" # 120
Funktionen exportieren (für Subshells)
#!/bin/bash
meine_funktion() {
echo "Hallo aus Funktion"
}
# Funktion exportieren
export -f meine_funktion
# In Subshell verfügbar
bash -c 'meine_funktion'
8. Arrays
Array erstellen
# Methode 1: Direkte Zuweisung
NAMEN=("Max" "Lisa" "Tom")
# Methode 2: Index-weise
NAMEN[0]="Max"
NAMEN[1]="Lisa"
NAMEN[2]="Tom"
# Methode 3: Aus Command
DATEIEN=($(ls))
# Methode 4: Leeres Array
declare -a ARRAY
Array-Elemente zugreifen
NAMEN=("Max" "Lisa" "Tom")
# Einzelnes Element
echo ${NAMEN[0]} # Max
echo ${NAMEN[1]} # Lisa
# Alle Elemente
echo ${NAMEN[@]} # Max Lisa Tom
echo ${NAMEN[*]} # Max Lisa Tom
# Anzahl Elemente
echo ${#NAMEN[@]} # 3
# Indizes
echo ${!NAMEN[@]} # 0 1 2
Array-Elemente hinzufügen/löschen
NAMEN=("Max" "Lisa")
# Hinzufügen
NAMEN+=("Tom")
NAMEN[3]="Anna"
# Löschen
unset NAMEN[1] # Lisa löschen
echo ${NAMEN[@]} # Max Tom Anna
# Komplett löschen
unset NAMEN
Über Array iterieren
NAMEN=("Max" "Lisa" "Tom")
# Methode 1: Über Werte
for NAME in "${NAMEN[@]}"; do
echo "Name: $NAME"
done
# Methode 2: Über Indizes
for i in "${!NAMEN[@]}"; do
echo "Index $i: ${NAMEN[$i]}"
done
# Methode 3: C-Style
for ((i=0; i<${#NAMEN[@]}; i++)); do
echo "Position $i: ${NAMEN[$i]}"
done
Array slicing
ZAHLEN=(0 1 2 3 4 5 6 7 8 9)
# Ab Index 2
echo ${ZAHLEN[@]:2} # 2 3 4 5 6 7 8 9
# Ab Index 2, Länge 3
echo ${ZAHLEN[@]:2:3} # 2 3 4
# Letztes Element
echo ${ZAHLEN[@]: -1} # 9
# Letzte 3 Elemente
echo ${ZAHLEN[@]: -3} # 7 8 9
Array sortieren
NAMEN=("Tom" "Anna" "Max" "Lisa")
# Sortieren
IFS=$'\n' SORTIERT=($(sort <<<"${NAMEN[*]}"))
unset IFS
echo ${SORTIERT[@]} # Anna Lisa Max Tom
Assoziative Arrays (Hash-Maps)
Bash 4+ unterstützt assoziative Arrays (Key-Value):
#!/bin/bash
# Deklarieren
declare -A PERSON
# Werte setzen
PERSON[name]="Max"
PERSON[alter]=25
PERSON[stadt]="Berlin"
# Zugriff
echo ${PERSON[name]} # Max
echo ${PERSON[alter]} # 25
# Alle Keys
echo ${!PERSON[@]} # name alter stadt
# Alle Values
echo ${PERSON[@]} # Max 25 Berlin
# Iterieren
for KEY in "${!PERSON[@]}"; do
echo "$KEY: ${PERSON[$KEY]}"
done
Mehrdimensionale Arrays (Workaround)
Bash hat keine echten mehrdimensionalen Arrays, aber:
#!/bin/bash
# Simulieren mit Delimiter
declare -A MATRIX
MATRIX[0,0]=1
MATRIX[0,1]=2
MATRIX[1,0]=3
MATRIX[1,1]=4
echo ${MATRIX[0,0]} # 1
echo ${MATRIX[1,1]} # 4
# Iterieren
for i in 0 1; do
for j in 0 1; do
echo "[$i,$j] = ${MATRIX[$i,$j]}"
done
done
9. String-Manipulation
String-Länge
TEXT="Hallo Welt"
echo ${#TEXT} # 10
Substring
TEXT="Hallo Welt"
# Ab Position 6
echo ${TEXT:6} # Welt
# Ab Position 0, Länge 5
echo ${TEXT:0:5} # Hallo
# Letzte 4 Zeichen
echo ${TEXT: -4} # Welt
String-Ersetzung
TEXT="Hallo Welt Welt"
# Erste Occurrence ersetzen
echo ${TEXT/Welt/World} # Hallo World Welt
# Alle ersetzen
echo ${TEXT//Welt/World} # Hallo World World
# Am Anfang ersetzen
echo ${TEXT/#Hallo/Hi} # Hi Welt Welt
# Am Ende ersetzen
echo ${TEXT/%Welt/World} # Hallo Welt World
String trimmen
TEXT=" Hallo Welt "
# Führende Leerzeichen
echo ${TEXT#"${TEXT%%[![:space:]]*}"}
# Trailing Leerzeichen
echo ${TEXT%"${TEXT##*[![:space:]]}"}
# Beide (Funktion)
trim() {
local var="$*"
var="${var#"${var%%[![:space:]]*}"}"
var="${var%"${var##*[![:space:]]}}"
echo "$var"
}
echo "$(trim "$TEXT")" # Hallo Welt
Prefix/Suffix entfernen
DATEI="test.txt.backup"
# Kürzestes Suffix entfernen (nicht-greedy)
echo ${DATEI%.*} # test.txt
# Längstes Suffix entfernen (greedy)
echo ${DATEI%%.*} # test
# Kürzestes Prefix entfernen
echo ${DATEI#*.} # txt.backup
# Längstes Prefix entfernen
echo ${DATEI##*.} # backup
Praktisches Beispiel - Dateinamen zerlegen:
DATEI="/pfad/zu/datei.txt"
# Nur Dateiname
echo ${DATEI##*/} # datei.txt
# Nur Pfad
echo ${DATEI%/*} # /pfad/zu
# Nur Name ohne Endung
BASENAME=${DATEI##*/}
echo ${BASENAME%.*} # datei
# Nur Endung
echo ${DATEI##*.} # txt
Case-Konvertierung
TEXT="Hallo Welt"
# Alles Kleinbuchstaben (Bash 4+)
echo ${TEXT,,} # hallo welt
# Alles Großbuchstaben (Bash 4+)
echo ${TEXT^^} # HALLO WELT
# Erster Buchstabe groß
echo ${TEXT^} # Hallo welt
# Erster Buchstabe jedes Wortes groß
echo ${TEXT^^[hw]} # Hallo Welt
String-Vergleich (erweitert)
TEXT="Hallo Welt"
# Beginnt mit?
if [[ $TEXT == Hallo* ]]; then
echo "Beginnt mit Hallo"
fi
# Endet mit?
if [[ $TEXT == *Welt ]]; then
echo "Endet mit Welt"
fi
# Enthält?
if [[ $TEXT == *ll* ]]; then
echo "Enthält 'll'"
fi
# Regex-Match
if [[ $TEXT =~ ^[A-Z] ]]; then
echo "Beginnt mit Großbuchstaben"
fi
String-Konkatenation
# Direkt
STR1="Hallo"
STR2="Welt"
GESAMT="$STR1 $STR2"
echo $GESAMT # Hallo Welt
# Anhängen
TEXT="Hallo"
TEXT+=" Welt"
echo $TEXT # Hallo Welt
# In Schleife
ERGEBNIS=""
for WORT in Hallo Welt wie gehts; do
ERGEBNIS+="$WORT "
done
echo $ERGEBNIS # Hallo Welt wie gehts
10. Mathematische Operationen
Arithmetische Expansion
# Methode 1: $((...))
SUMME=$((5 + 3))
echo $SUMME # 8
PRODUKT=$((5 * 3))
echo $PRODUKT # 15
# Methode 2: let
let SUMME=5+3
echo $SUMME # 8
# Methode 3: ((...))
((i = 5 + 3))
echo $i # 8
# Methode 4: expr (veraltet)
SUMME=$(expr 5 + 3)
echo $SUMME # 8
Grundrechenarten
A=10
B=3
# Addition
echo $((A + B)) # 13
# Subtraktion
echo $((A - B)) # 7
# Multiplikation
echo $((A * B)) # 30
# Division (ganzzahlig!)
echo $((A / B)) # 3
# Modulo (Rest)
echo $((A % B)) # 1
# Potenz
echo $((A ** 2)) # 100
Inkrement/Dekrement
i=5
# Pre-Inkrement
echo $((++i)) # 6, i ist jetzt 6
# Post-Inkrement
echo $((i++)) # 6, i ist jetzt 7
echo $i # 7
# Pre-Dekrement
echo $((--i)) # 6, i ist jetzt 6
# Post-Dekrement
echo $((i--)) # 6, i ist jetzt 5
echo $i # 5
# Mit Zuweisung
((i += 5)) # i = i + 5
((i -= 2)) # i = i - 2
((i *= 3)) # i = i * 3
((i /= 2)) # i = i / 2
Gleitkomma-Arithmetik mit bc
# bc installieren falls nötig
# apt install bc
# Division mit Nachkommastellen
echo "scale=2; 10 / 3" | bc # 3.33
# Komplexere Berechnung
echo "scale=4; sqrt(2)" | bc # 1.4142
# Mit Variablen
A=10
B=3
ERGEBNIS=$(echo "scale=2; $A / $B" | bc)
echo $ERGEBNIS # 3.33
# Funktion für Division
fdiv() {
echo "scale=2; $1 / $2" | bc
}
echo $(fdiv 10 3) # 3.33
Mathematische Funktionen mit bc
# Quadratwurzel
echo "sqrt(16)" | bc # 4
# Sinus (in Radiant)
echo "s(1.5708)" | bc -l # ~1 (sin(π/2))
# Cosinus
echo "c(0)" | bc -l # 1
# Natürlicher Logarithmus
echo "l(2.71828)" | bc -l # ~1
# e-Funktion
echo "e(1)" | bc -l # 2.71828
Vergleiche in Arithmetik
A=10
B=20
# In if-Bedingung
if ((A < B)); then
echo "A ist kleiner"
fi
# Als Ausdruck
((A > B)) && echo "A größer" || echo "B größer"
# Mehrere Bedingungen
if ((A > 5 && A < 15)); then
echo "A ist zwischen 5 und 15"
fi
Zufallszahlen
# Zufallszahl 0-32767
echo $RANDOM
# Zufallszahl in Bereich (0-99)
echo $((RANDOM % 100))
# Zufallszahl in Bereich (10-50)
echo $((RANDOM % 41 + 10))
# Funktion für Range
random_range() {
local MIN=$1
local MAX=$2
echo $((RANDOM % (MAX - MIN + 1) + MIN))
}
echo $(random_range 1 100)
11. Dateien und Verzeichnisse
Datei lesen
# Komplette Datei
INHALT=$(cat datei.txt)
# Zeile für Zeile (empfohlen)
while read -r ZEILE; do
echo "$ZEILE"
done < datei.txt
# Erste Zeile
ERSTE=$(head -n 1 datei.txt)
# Letzte Zeile
LETZTE=$(tail -n 1 datei.txt)
# Bestimmte Zeile (z.B. Zeile 5)
ZEILE=$(sed -n '5p' datei.txt)
Datei schreiben
# Überschreiben
echo "Hallo Welt" > datei.txt
# Anhängen
echo "Neue Zeile" >> datei.txt
# Mehrere Zeilen
cat > datei.txt << EOF
Zeile 1
Zeile 2
Zeile 3
EOF
# Mit Variablen
NAME="Max"
cat > config.txt << EOF
Name: $NAME
Datum: $(date)
EOF
# Aus Schleife
for i in {1..10}; do
echo "Zeile $i" >> datei.txt
done
Datei existiert prüfen
DATEI="test.txt"
if [[ -f "$DATEI" ]]; then
echo "Datei existiert"
else
echo "Datei existiert nicht"
fi
# Erstellen falls nicht vorhanden
[[ -f "$DATEI" ]] || touch "$DATEI"
Verzeichnis erstellen
# Einfach
mkdir mein-ordner
# Mit Eltern-Verzeichnissen
mkdir -p /pfad/zum/tiefen/ordner
# Nur wenn nicht existiert
[[ -d "ordner" ]] || mkdir ordner
# In Script
BACKUP_DIR="/backup/$(date +%Y-%m-%d)"
mkdir -p "$BACKUP_DIR"
Dateien kopieren/verschieben
# Kopieren
cp quelle.txt ziel.txt
# Rekursiv kopieren
cp -r quell-ordner ziel-ordner
# Mit Backup
cp --backup=numbered datei.txt datei.txt
# Verschieben
mv alt.txt neu.txt
# In Verzeichnis verschieben
mv datei.txt /tmp/
# Mehrere Dateien
mv *.txt /backup/
Dateien löschen
# Datei löschen
rm datei.txt
# Mehrere Dateien
rm datei1.txt datei2.txt
# Mit Wildcard
rm *.tmp
# Verzeichnis löschen (leer)
rmdir ordner
# Verzeichnis löschen (mit Inhalt)
rm -r ordner
# Sicher löschen (mit Rückfrage)
rm -i datei.txt
# Force (keine Rückfrage)
rm -f datei.txt
Dateien finden
# Alle .txt Dateien im aktuellen Verzeichnis
find . -name "*.txt"
# Alle .txt Dateien rekursiv
find /pfad -name "*.txt"
# Case-insensitive
find . -iname "*.TXT"
# Nur Dateien (keine Verzeichnisse)
find . -type f -name "*.txt"
# Nur Verzeichnisse
find . -type d
# Geändert in letzten 7 Tagen
find . -mtime -7
# Größer als 10MB
find . -size +10M
# Mit Aktion
find . -name "*.tmp" -delete
find . -name "*.txt" -exec chmod 644 {} \;
Dateiattribute
DATEI="test.txt"
# Größe
GROESSE=$(stat -c %s "$DATEI")
echo "Größe: $GROESSE Bytes"
# Änderungszeit
MTIME=$(stat -c %Y "$DATEI")
echo "Geändert: $(date -d @$MTIME)"
# Berechtigungen
PERMS=$(stat -c %a "$DATEI")
echo "Rechte: $PERMS"
# Owner
OWNER=$(stat -c %U "$DATEI")
echo "Besitzer: $OWNER"
Temporäre Dateien
# Temporäre Datei erstellen
TEMP=$(mktemp)
echo "Temp-Datei: $TEMP"
# Temporäres Verzeichnis
TEMPDIR=$(mktemp -d)
echo "Temp-Verzeichnis: $TEMPDIR"
# Am Ende aufräumen
trap "rm -f $TEMP" EXIT
# Oder manuell
rm -f "$TEMP"
Arbeitsverzeichnis
# Aktuelles Verzeichnis
PWD=$(pwd)
echo "Aktuell: $PWD"
# Wechseln
cd /tmp
# Zurück zum vorherigen
cd -
# Zum Home
cd ~
# oder
cd
# In Script: Immer ins Script-Verzeichnis wechseln
cd "$(dirname "$0")"
12. Input/Output und Redirection
Standard-Streams
# 0 = stdin (Standard-Input)
# 1 = stdout (Standard-Output)
# 2 = stderr (Standard-Error)
Output umleiten
# stdout in Datei (überschreiben)
echo "Hallo" > datei.txt
# stdout anhängen
echo "Welt" >> datei.txt
# stderr in Datei
befehl 2> fehler.log
# stdout UND stderr in Datei
befehl > ausgabe.log 2>&1
# oder (Bash 4+)
befehl &> ausgabe.log
# stdout und stderr getrennt
befehl > ausgabe.log 2> fehler.log
# stderr nach stdout umleiten
befehl 2>&1 | less
Input umleiten
# Aus Datei lesen
sort < unsortiert.txt
# Here Document
cat << EOF
Zeile 1
Zeile 2
EOF
# Here String
grep "test" <<< "test string"
# In Variable
VAR=$(cat << EOF
Mehrzeilig
Text
EOF
)
Pipes
# Output als Input
ls -la | grep ".txt"
# Mehrere Pipes
cat datei.txt | grep "fehler" | wc -l
# Mit tee (Output duplizieren)
echo "Hallo" | tee datei.txt # Zeigt UND schreibt
# tee anhängen
echo "Welt" | tee -a datei.txt
/dev/null (Verwerfen)
# Output verwerfen
befehl > /dev/null
# Errors verwerfen
befehl 2> /dev/null
# Alles verwerfen
befehl &> /dev/null
# Nur Errors behalten
befehl 2>&1 > /dev/null
Process Substitution
# Output eines Befehls als Datei-Input
diff <(ls /tmp) <(ls /var/tmp)
# Mehrere Inputs
cat <(echo "Datei 1") <(echo "Datei 2")
# In while-Schleife (behält Variablen)
COUNTER=0
while read -r ZEILE; do
((COUNTER++))
done < <(cat datei.txt)
echo "Zeilen: $COUNTER" # Funktioniert!
Named Pipes (FIFO)
# FIFO erstellen
mkfifo meine-pipe
# Terminal 1: Schreiben
echo "Hallo" > meine-pipe
# Terminal 2: Lesen
cat < meine-pipe
# Aufräumen
rm meine-pipe
Exec für Redirection
#!/bin/bash
# File Descriptor öffnen
exec 3< input.txt # FD 3 zum Lesen
exec 4> output.txt # FD 4 zum Schreiben
# Verwenden
while read -r ZEILE <&3; do
echo "Gelesen: $ZEILE" >&4
done
# Schließen
exec 3<&-
exec 4>&-
13. Error Handling
Exit-Codes
# Eigene Exit-Codes setzen
exit 0 # Erfolg
exit 1 # Allgemeiner Fehler
exit 2 # Missbrauch von Shell-Befehl
exit 126 # Befehl nicht ausführbar
exit 127 # Befehl nicht gefunden
exit 130 # Script mit Ctrl+C beendet
# Exit-Code des letzten Befehls prüfen
ls /tmp
if [[ $? -eq 0 ]]; then
echo "Erfolgreich"
fi
set -Optionen
#!/bin/bash
# Exit bei Fehler
set -e
# oder
set -o errexit
# Exit bei undefined Variable
set -u
# oder
set -o nounset
# Pipe-Fehler erkennen
set -o pipefail
# Kombiniert (empfohlen!)
set -euo pipefail
# Debug-Modus
set -x
# oder
set -o xtrace
Beispiel:
#!/bin/bash
set -euo pipefail
# Script stoppt hier wenn Fehler
cd /nicht/existent # Fehler!
echo "Das wird nie ausgeführt"
trap - Cleanup bei Exit
#!/bin/bash
# Funktion für Cleanup
cleanup() {
echo "Räume auf..."
rm -f /tmp/lock
}
# Bei Exit aufrufen
trap cleanup EXIT
# Script läuft
echo "Script läuft..."
touch /tmp/lock
sleep 10
# cleanup() wird automatisch ausgeführt
# - bei normalem Exit
# - bei Ctrl+C
# - bei Fehler (mit set -e)
trap - Signale abfangen
#!/bin/bash
# Funktion für Interrupt
handle_interrupt() {
echo ""
echo "Unterbrochen! Räume auf..."
exit 130
}
# Ctrl+C (SIGINT) abfangen
trap handle_interrupt INT
# Endlosschleife
while true; do
echo "Läuft... (Ctrl+C zum Beenden)"
sleep 1
done
Wichtige Signale:
INT- Ctrl+CTERM- Termination signalEXIT- Script-Ende (jeder Exit)ERR- Bei Fehler (mit set -E)DEBUG- Vor jedem Befehl
Fehler mit if abfangen
#!/bin/bash
if cp quelle.txt ziel.txt; then
echo "Kopiert"
else
echo "Fehler beim Kopieren" >&2
exit 1
fi
# Kurzform mit ||
cp quelle.txt ziel.txt || {
echo "Fehler!" >&2
exit 1
}
Fehler mit || und &&
# Bei Erfolg weitermachen
befehl1 && befehl2
# Bei Fehler Alternativ-Befehl
befehl1 || echo "Fehlgeschlagen"
# Kombiniert
befehl1 && echo "Erfolg" || echo "Fehler"
# In Funktion
datei_kopieren() {
cp "$1" "$2" && echo "OK" || return 1
}
Fehler-Nachrichten
#!/bin/bash
# Funktion für Fehler
error() {
echo "FEHLER: $*" >&2
exit 1
}
# Funktion für Warnung
warning() {
echo "WARNUNG: $*" >&2
}
# Verwendung
[[ -f "wichtig.txt" ]] || error "Datei wichtig.txt fehlt!"
[[ -d "/tmp" ]] || warning "Kein /tmp Verzeichnis"
Assert-Funktion
#!/bin/bash
assert() {
if ! "$@"; then
echo "Assertion failed: $*" >&2
exit 1
fi
}
# Verwendung
assert [[ -f "config.txt" ]]
assert [[ $PORT -gt 0 ]]
Try-Catch simulieren
#!/bin/bash
try() {
[[ $- = *e* ]]; SAVED_OPT_E=$?
set +e
}
catch() {
export exception_code=$?
(( SAVED_OPT_E )) && set +e
return $exception_code
}
# Verwendung
try
(
# Code der fehlschlagen könnte
cd /nicht/existent
echo "Nicht erreicht"
)
catch || {
case $exception_code in
1)
echo "Fehler 1: Verzeichnis nicht gefunden"
;;
*)
echo "Unbekannter Fehler: $exception_code"
;;
esac
}
14. Reguläre Ausdrücke
Regex in [[ ]]
#!/bin/bash
TEXT="Hallo123"
# Einfaches Match
if [[ $TEXT =~ [0-9] ]]; then
echo "Enthält Zahlen"
fi
# Komplexeres Pattern
if [[ $TEXT =~ ^[A-Z][a-z]+[0-9]+$ ]]; then
echo "Passt: Großbuchstabe, Kleinbuchstaben, Zahlen"
fi
# Capture Groups
EMAIL="max@example.com"
if [[ $EMAIL =~ ^([a-zA-Z0-9._%+-]+)@([a-zA-Z0-9.-]+)\.([a-zA-Z]{2,})$ ]]; then
echo "User: ${BASH_REMATCH[1]}"
echo "Domain: ${BASH_REMATCH[2]}"
echo "TLD: ${BASH_REMATCH[3]}"
fi
grep mit Regex
# Einfaches grep
grep "muster" datei.txt
# Case-insensitive
grep -i "muster" datei.txt
# Ganze Wörter
grep -w "wort" datei.txt
# Zeilen die NICHT matchen
grep -v "muster" datei.txt
# Extended Regex
grep -E "muster1|muster2" datei.txt
# Perl-compatible Regex
grep -P "\d{3}-\d{4}" datei.txt
# Mit Zeilennummern
grep -n "muster" datei.txt
# Nur Dateinamen
grep -l "muster" *.txt
# Rekursiv
grep -r "muster" /pfad/
sed - Stream Editor
# Ersetzen (erste Occurrence)
sed 's/alt/neu/' datei.txt
# Ersetzen (alle)
sed 's/alt/neu/g' datei.txt
# Ersetzen (case-insensitive)
sed 's/alt/neu/gi' datei.txt
# In-place editieren
sed -i 's/alt/neu/g' datei.txt
# Mit Backup
sed -i.bak 's/alt/neu/g' datei.txt
# Zeilen löschen
sed '/muster/d' datei.txt
# Bestimmte Zeile
sed '5d' datei.txt # Zeile 5 löschen
sed -n '5p' datei.txt # Nur Zeile 5 anzeigen
# Bereich
sed -n '10,20p' datei.txt # Zeilen 10-20
awk - Text-Verarbeitung
# Erste Spalte
awk '{print $1}' datei.txt
# Mehrere Spalten
awk '{print $1, $3}' datei.txt
# Mit Bedingung
awk '$3 > 100 {print $1}' datei.txt
# Custom Delimiter
awk -F: '{print $1}' /etc/passwd
# Summe berechnen
awk '{sum += $1} END {print sum}' zahlen.txt
# Mit Regex
awk '/ERROR/ {print $0}' logfile.txt
Regex-Pattern Beispiele
# E-Mail
[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}
# URL
https?://[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(/.*)?
# IP-Adresse (einfach)
[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}
# IP-Adresse (korrekt)
((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)
# Datum (YYYY-MM-DD)
[0-9]{4}-[0-9]{2}-[0-9]{2}
# Zeit (HH:MM:SS)
[0-9]{2}:[0-9]{2}:[0-9]{2}
# Telefonnummer (DE)
(\+49|0)[1-9][0-9]{1,14}
# Hexadezimal
#?[0-9a-fA-F]{6}
# UUID
[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}
15. Netzwerk und Ports
Port prüfen
#!/bin/bash
# Methode 1: Mit netstat
check_port_netstat() {
netstat -tuln | grep -q ":$1 "
}
# Methode 2: Mit ss (moderner)
check_port_ss() {
ss -tuln | grep -q ":$1 "
}
# Methode 3: Mit lsof
check_port_lsof() {
lsof -i ":$1" > /dev/null 2>&1
}
# Methode 4: Mit /dev/tcp (nur Bash)
check_port_tcp() {
timeout 1 bash -c "echo >/dev/tcp/localhost/$1" 2>/dev/null
}
# Verwenden
if check_port_ss 80; then
echo "Port 80 ist belegt"
else
echo "Port 80 ist frei"
fi
Nächsten freien Port finden
#!/bin/bash
find_free_port() {
local PORT=${1:-60000} # Startport
while ss -tuln | grep -q ":$PORT "; do
((PORT++))
# Sicherheit: Max Port
if [[ $PORT -gt 65535 ]]; then
echo "Kein freier Port gefunden!" >&2
return 1
fi
done
echo $PORT
}
# Verwenden
FREIER_PORT=$(find_free_port 60000)
echo "Nächster freier Port: $FREIER_PORT"
Optimierte Version (aus deinem Beispiel)
#!/bin/bash
# Startport
PORT=60000
# Funktion zum Überprüfen, ob ein Port frei ist
is_port_free() {
! ss -tla | awk '{print $4}' | grep -q ":$1$"
}
# Schleife, um den nächsten freien Port zu finden
while ! is_port_free $PORT; do
((PORT++))
# Sicherheit
if [[ $PORT -gt 65535 ]]; then
echo "Fehler: Kein freier Port bis 65535!" >&2
exit 1
fi
done
echo "Next free port: $PORT"
HTTP-Request senden
#!/bin/bash
# Mit curl
RESPONSE=$(curl -s https://api.example.com/data)
echo $RESPONSE
# Mit Status-Code
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" https://example.com)
if [[ $HTTP_CODE -eq 200 ]]; then
echo "OK"
fi
# Mit wget
wget -q -O - https://example.com
# POST-Request
curl -X POST -d "param=value" https://api.example.com
Ping-Check
#!/bin/bash
ping_host() {
ping -c 1 -W 1 "$1" > /dev/null 2>&1
}
if ping_host "8.8.8.8"; then
echo "Host erreichbar"
else
echo "Host nicht erreichbar"
fi
DNS-Lookup
#!/bin/bash
# Mit host
IP=$(host google.com | awk '/has address/ {print $4}' | head -n1)
echo "Google IP: $IP"
# Mit dig
IP=$(dig +short google.com | head -n1)
echo "Google IP: $IP"
# Mit nslookup
nslookup google.com
Download mit Fortschritt
#!/bin/bash
# Mit curl
curl -O https://example.com/file.zip
# Mit wget
wget https://example.com/file.zip
# Mit Fortschrittsbalken
wget --progress=bar:force https://example.com/file.zip
# Zu spezifischem Pfad
curl -o /tmp/download.zip https://example.com/file.zip
16. Prozesse und Jobs
Prozess-Informationen
# Alle laufenden Prozesse
ps aux
# Eigene Prozesse
ps ux
# Nach Namen filtern
ps aux | grep firefox
# Mit pgrep
pgrep firefox
# PID von Prozess
pidof firefox
# Prozess-Baum
pstree
pstree -p # Mit PIDs
Prozesse starten
# Normal (Vordergrund)
./script.sh
# Im Hintergrund
./script.sh &
# PID merken
./script.sh &
PID=$!
echo "Started with PID: $PID"
# Detached (überlebt Shell-Exit)
nohup ./script.sh &
# Mit Screen
screen -dmS myscript ./script.sh
# Mit Tmux
tmux new-session -d -s myscript ./script.sh
Prozesse kontrollieren
# Stoppen (SIGTERM)
kill $PID
# Force Kill (SIGKILL)
kill -9 $PID
# Alle Prozesse mit Namen
pkill firefox
killall firefox
# Mit Signal
pkill -TERM firefox
pkill -HUP nginx
# Prozess pausieren
kill -STOP $PID
# Prozess fortsetzen
kill -CONT $PID
Jobs verwalten
# Prozess im Hintergrund starten
./script.sh &
# Laufende Jobs anzeigen
jobs
# Job in Vordergrund holen
fg %1
# Job in Hintergrund schicken
bg %1
# Aktuellen Prozess in Hintergrund
# Ctrl+Z (pausiert)
# bg (fortsetzten im Hintergrund)
# Job beenden
kill %1
wait - Auf Prozesse warten
#!/bin/bash
# Prozesse starten
./script1.sh &
PID1=$!
./script2.sh &
PID2=$!
# Auf beide warten
wait $PID1
wait $PID2
echo "Beide fertig"
# Oder auf alle warten
./script1.sh &
./script2.sh &
wait
echo "Alle fertig"
Parallel ausführen
#!/bin/bash
# Einfach
for i in {1..10}; do
./process.sh $i &
done
wait
# Mit Limit (max 4 parallel)
parallel_limit() {
local MAX=4
local COUNT=0
for i in {1..20}; do
./process.sh $i &
((COUNT++))
if [[ $COUNT -ge $MAX ]]; then
wait -n # Warte auf einen
((COUNT--))
fi
done
wait
}
Prozess-Output in Variable
# Synchron (wartet)
OUTPUT=$(./script.sh)
# Mit Fehlerbehandlung
if OUTPUT=$(./script.sh 2>&1); then
echo "Erfolg: $OUTPUT"
else
echo "Fehler: $OUTPUT"
fi
timeout - Prozess mit Zeitlimit
# Max 10 Sekunden
timeout 10 ./script.sh
# Mit Signal (SIGKILL nach 5s)
timeout -k 5 10 ./script.sh
# In if-Bedingung
if timeout 30 ./script.sh; then
echo "Erfolgreich"
else
EXIT_CODE=$?
if [[ $EXIT_CODE -eq 124 ]]; then
echo "Timeout!"
else
echo "Fehler: $EXIT_CODE"
fi
fi
17. Debugging
set -x (Trace Mode)
#!/bin/bash
# Debug für ganzes Script
set -x
echo "Befehl 1"
echo "Befehl 2"
# Debug ausschalten
set +x
echo "Dieser wird nicht getraced"
Ausgabe:
+ echo 'Befehl 1'
Befehl 1
+ echo 'Befehl 2'
Befehl 2
+ set +x
Dieser wird nicht getraced
Selektives Debugging
#!/bin/bash
debug() {
[[ $DEBUG ]] && echo "DEBUG: $*" >&2
}
debug "Script gestartet"
# Aufruf mit Debug:
DEBUG=1 ./script.sh
PS4 für bessere Debug-Ausgabe
#!/bin/bash
# Zeige Datei:Zeile vor jedem Befehl
export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
set -x
echo "Test"
my_function() {
echo "In Funktion"
}
my_function
set +x
Ausgabe:
+(script.sh:5): echo Test
Test
+(script.sh:9): my_function
+(script.sh:7): my_function(): echo 'In Funktion'
In Funktion
bashdb - Debugger
# bashdb installieren
apt install bashdb
# Script debuggen
bashdb ./script.sh
# Befehle in bashdb:
# s - step (nächste Zeile)
# n - next (überspringt Funktionen)
# c - continue (bis Breakpoint)
# l - list (Code anzeigen)
# p VAR - print (Variable anzeigen)
# q - quit
Logging-Funktion
#!/bin/bash
# Log-Level
LOG_LEVEL=${LOG_LEVEL:-INFO}
log() {
local LEVEL=$1
shift
local MESSAGE="$*"
local TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$TIMESTAMP] [$LEVEL] $MESSAGE" | tee -a script.log
}
log_debug() {
[[ $LOG_LEVEL == "DEBUG" ]] && log DEBUG "$@"
}
log_info() {
log INFO "$@"
}
log_error() {
log ERROR "$@" >&2
}
# Verwendung
log_debug "Debug-Nachricht"
log_info "Script gestartet"
log_error "Fehler aufgetreten!"
Assertion-Debugging
#!/bin/bash
assert() {
if ! "$@"; then
echo "ASSERTION FAILED: $*" >&2
echo " File: ${BASH_SOURCE[1]}" >&2
echo " Line: ${BASH_LINENO[0]}" >&2
echo " Function: ${FUNCNAME[1]}" >&2
exit 1
fi
}
# Verwendung
FILE="test.txt"
assert [[ -f "$FILE" ]]
assert [[ $(wc -l < "$FILE") -gt 0 ]]
ShellCheck - Statische Analyse
# ShellCheck installieren
apt install shellcheck
# Script prüfen
shellcheck script.sh
# Mit Severity-Level
shellcheck -S error script.sh
# Bestimmte Checks ausschließen
shellcheck -e SC2034 script.sh
# In Script: Zeile ignorieren
# shellcheck disable=SC2034
UNUSED_VAR="test"
18. Best Practices
Script-Template
#!/usr/bin/env bash
#############################################
# Script: mein-script.sh
# Beschreibung: Was macht das Script
# Autor: Name
# Datum: 2024-01-01
# Version: 1.0
#############################################
# Strikte Fehlerbehandlung
set -euo pipefail
# Script-Verzeichnis ermitteln
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Variablen
readonly VERSION="1.0"
readonly SCRIPT_NAME=$(basename "$0")
# Farben für Output
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly NC='\033[0m' # No Color
# Logging-Funktionen
log_info() {
echo -e "${GREEN}[INFO]${NC} $*"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $*" >&2
}
log_error() {
echo -e "${RED}[ERROR]${NC} $*" >&2
}
# Cleanup-Funktion
cleanup() {
log_info "Cleanup..."
# Temporäre Dateien löschen, etc.
}
# Cleanup bei Exit
trap cleanup EXIT
# Hilfe-Funktion
usage() {
cat << EOF
Usage: $SCRIPT_NAME [OPTIONS] <parameter>
Beschreibung des Scripts.
OPTIONS:
-h, --help Zeigt diese Hilfe
-v, --version Zeigt Version
-d, --debug Debug-Modus
EXAMPLES:
$SCRIPT_NAME --help
$SCRIPT_NAME parameter
EOF
}
# Parameter-Parsing
main() {
# Parameter prüfen
if [[ $# -eq 0 ]]; then
usage
exit 1
fi
# Parameter parsen
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
usage
exit 0
;;
-v|--version)
echo "$SCRIPT_NAME version $VERSION"
exit 0
;;
-d|--debug)
set -x
shift
;;
*)
# Hauptlogik hier
log_info "Verarbeite: $1"
shift
;;
esac
done
}
# Script ausführen
main "$@"
Variablen-Naming
# KONSTANTEN in UPPERCASE
readonly MAX_RETRIES=3
readonly CONFIG_FILE="/etc/app/config"
# Globale Variablen in UPPERCASE
COUNTER=0
TOTAL_SIZE=0
# Lokale Variablen in lowercase
local temp_file="/tmp/xyz"
local user_name="max"
# Funktions-Parameter auch lowercase
process_file() {
local file_path="$1"
local output_dir="$2"
}
Funktionen organisieren
#!/bin/bash
#######################
# Hilfsfunktionen
#######################
log_info() {
echo "[INFO] $*"
}
log_error() {
echo "[ERROR] $*" >&2
}
#######################
# Validierungsfunktionen
#######################
validate_file() {
[[ -f "$1" ]] || { log_error "Datei nicht gefunden: $1"; return 1; }
}
validate_port() {
[[ $1 =~ ^[0-9]+$ ]] && [[ $1 -gt 0 ]] && [[ $1 -lt 65536 ]]
}
#######################
# Hauptfunktionen
#######################
process_data() {
local input="$1"
# ...
}
#######################
# Main
#######################
main() {
# ...
}
main "$@"
Sicherheits-Best-Practices
#!/bin/bash
# 1. Strikte Fehlerbehandlung
set -euo pipefail
# 2. Variablen immer quoten
FILE="test file.txt"
cat "$FILE" # RICHTIG
# cat $FILE # FALSCH (bricht bei Leerzeichen)
# 3. Arrays für Listen
FILES=("file1.txt" "file2.txt" "file 3.txt")
for FILE in "${FILES[@]}"; do # RICHTIG
echo "$FILE"
done
# 4. Temporäre Dateien sicher erstellen
TEMP=$(mktemp)
trap "rm -f $TEMP" EXIT
# 5. User-Input validieren
read -p "Port: " PORT
if ! [[ $PORT =~ ^[0-9]+$ ]]; then
echo "Ungültige Eingabe!" >&2
exit 1
fi
# 6. Berechtigungen prüfen
if [[ ! -r "$FILE" ]]; then
echo "Datei nicht lesbar: $FILE" >&2
exit 1
fi
# 7. Relative Pfade vermeiden
cd "$(dirname "$0")" || exit 1
# 8. Externe Commands validieren
if ! command -v docker &> /dev/null; then
echo "Docker nicht installiert!" >&2
exit 1
fi
# 9. Secrets nicht in Variablen speichern wenn möglich
# Besser: Aus Datei lesen
PASSWORD=$(cat /secure/password.txt)
# 10. sudo vermeiden wo möglich
# Statt sudo für alles, Capabilities nutzen
Performance-Tipps
# LANGSAM: Externe Befehle in Schleife
for i in {1..1000}; do
echo $i | grep "5"
done
# SCHNELL: Bash-interne Features
for i in {1..1000}; do
[[ $i == *5* ]] && echo $i
done
# LANGSAM: Mehrfach Datei lesen
grep "ERROR" logfile.txt
grep "WARNING" logfile.txt
# SCHNELL: Einmal lesen
grep -E "ERROR|WARNING" logfile.txt
# LANGSAM: Viele kleine Writes
for i in {1..1000}; do
echo $i >> file.txt
done
# SCHNELL: Batch-Write
{
for i in {1..1000}; do
echo $i
done
} >> file.txt
Portabilität
#!/usr/bin/env bash
# POSIX-kompatibel vs. Bash-spezifisch
# POSIX (läuft überall):
if [ "$VAR" = "test" ]; then
echo "OK"
fi
# Bash (nur in Bash):
if [[ $VAR == "test" ]]; then
echo "OK"
fi
# Prüfen ob in Bash
if [ -z "$BASH_VERSION" ]; then
echo "Dieses Script braucht Bash!" >&2
exit 1
fi
# Feature-Detection statt OS-Detection
if command -v apt-get &> /dev/null; then
# Debian/Ubuntu
apt-get install package
elif command -v yum &> /dev/null; then
# RHEL/CentOS
yum install package
fi
19. Praktische Beispiele
Beispiel 1: Backup-Script ⭐
#!/usr/bin/env bash
set -euo pipefail
# Konfiguration
readonly BACKUP_SOURCE="/home/user/important"
readonly BACKUP_DEST="/backup"
readonly MAX_BACKUPS=7
readonly LOG_FILE="/var/log/backup.log"
# Logging
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}
# Cleanup alte Backups
cleanup_old_backups() {
log "Cleanup alte Backups..."
local COUNT=$(find "$BACKUP_DEST" -name "backup-*.tar.gz" | wc -l)
if [[ $COUNT -gt $MAX_BACKUPS ]]; then
find "$BACKUP_DEST" -name "backup-*.tar.gz" -type f -printf '%T+ %p\n' \
| sort | head -n -$MAX_BACKUPS | awk '{print $2}' \
| xargs rm -f
log "$(($COUNT - $MAX_BACKUPS)) alte Backups gelöscht"
fi
}
# Hauptfunktion
create_backup() {
local TIMESTAMP=$(date +%Y%m%d_%H%M%S)
local BACKUP_FILE="$BACKUP_DEST/backup-$TIMESTAMP.tar.gz"
log "Starte Backup..."
# Backup erstellen
if tar -czf "$BACKUP_FILE" -C "$(dirname "$BACKUP_SOURCE")" "$(basename "$BACKUP_SOURCE")"; then
local SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
log "Backup erfolgreich: $BACKUP_FILE ($SIZE)"
else
log "ERROR: Backup fehlgeschlagen!"
return 1
fi
# Alte Backups aufräumen
cleanup_old_backups
}
# Main
main() {
# Verzeichnis prüfen
[[ -d "$BACKUP_SOURCE" ]] || { log "ERROR: Quelle nicht gefunden"; exit 1; }
[[ -d "$BACKUP_DEST" ]] || mkdir -p "$BACKUP_DEST"
# Backup erstellen
create_backup
log "Backup-Prozess abgeschlossen"
}
main "$@"
Beispiel 2: Server Health-Check
#!/usr/bin/env bash
set -euo pipefail
# Konfiguration
readonly CPU_THRESHOLD=80
readonly MEM_THRESHOLD=90
readonly DISK_THRESHOLD=85
# Farben
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly NC='\033[0m'
# Status ausgeben
print_status() {
local STATUS=$1
local MESSAGE=$2
case $STATUS in
OK)
echo -e "${GREEN}✓${NC} $MESSAGE"
;;
WARN)
echo -e "${YELLOW}⚠${NC} $MESSAGE"
;;
CRITICAL)
echo -e "${RED}✗${NC} $MESSAGE"
;;
esac
}
# CPU-Last prüfen
check_cpu() {
local CPU_LOAD=$(top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print 100 - $1}')
local CPU_INT=${CPU_LOAD%.*}
if [[ $CPU_INT -lt $CPU_THRESHOLD ]]; then
print_status OK "CPU: ${CPU_LOAD}%"
else
print_status CRITICAL "CPU: ${CPU_LOAD}% (Schwellwert: ${CPU_THRESHOLD}%)"
fi
}
# RAM-Nutzung prüfen
check_memory() {
local MEM_USED=$(free | grep Mem | awk '{printf("%.0f", $3/$2 * 100)}')
if [[ $MEM_USED -lt $MEM_THRESHOLD ]]; then
print_status OK "RAM: ${MEM_USED}%"
else
print_status CRITICAL "RAM: ${MEM_USED}% (Schwellwert: ${MEM_THRESHOLD}%)"
fi
}
# Disk-Space prüfen
check_disk() {
while read -r LINE; do
local USAGE=$(echo "$LINE" | awk '{print $5}' | sed 's/%//')
local MOUNT=$(echo "$LINE" | awk '{print $6}')
if [[ $USAGE -lt $DISK_THRESHOLD ]]; then
print_status OK "Disk $MOUNT: ${USAGE}%"
else
print_status CRITICAL "Disk $MOUNT: ${USAGE}% (Schwellwert: ${DISK_THRESHOLD}%)"
fi
done < <(df -h | grep -vE '^Filesystem|tmpfs|cdrom')
}
# Services prüfen
check_services() {
local SERVICES=("ssh" "nginx" "mysql")
for SERVICE in "${SERVICES[@]}"; do
if systemctl is-active --quiet "$SERVICE"; then
print_status OK "Service $SERVICE läuft"
else
print_status CRITICAL "Service $SERVICE läuft NICHT"
fi
done
}
# Main
main() {
echo "=== Server Health Check ==="
echo "Zeitpunkt: $(date)"
echo ""
echo "--- CPU ---"
check_cpu
echo ""
echo "--- Memory ---"
check_memory
echo ""
echo "--- Disk Space ---"
check_disk
echo ""
echo "--- Services ---"
check_services
}
main "$@"
Beispiel 3: Log-Analyzer
#!/usr/bin/env bash
set -euo pipefail
# Konfiguration
readonly LOG_FILE="${1:-/var/log/syslog}"
readonly REPORT_FILE="log_report_$(date +%Y%m%d_%H%M%S).txt"
# Report generieren
generate_report() {
{
echo "=== Log Analysis Report ==="
echo "File: $LOG_FILE"
echo "Generated: $(date)"
echo ""
echo "--- Top 10 Error Messages ---"
grep -i "error" "$LOG_FILE" | sort | uniq -c | sort -rn | head -10
echo ""
echo "--- Top 10 Warning Messages ---"
grep -i "warning" "$LOG_FILE" | sort | uniq -c | sort -rn | head -10
echo ""
echo "--- Activity by Hour ---"
awk '{print $3}' "$LOG_FILE" | cut -d: -f1 | sort | uniq -c
echo ""
echo "--- Failed SSH Logins ---"
grep "Failed password" "$LOG_FILE" | awk '{print $(NF-3)}' | sort | uniq -c | sort -rn
echo ""
echo "--- Disk Space Warnings ---"
grep -i "disk.*full\|no space left" "$LOG_FILE"
} > "$REPORT_FILE"
echo "Report erstellt: $REPORT_FILE"
}
# Main
main() {
if [[ ! -f "$LOG_FILE" ]]; then
echo "Log-Datei nicht gefunden: $LOG_FILE" >&2
exit 1
fi
generate_report
}
main "$@"
Beispiel 4: Batch Image Converter
#!/usr/bin/env bash
set -euo pipefail
# Konfiguration
readonly INPUT_DIR="${1:-.}"
readonly OUTPUT_DIR="${2:-./converted}"
readonly FORMAT="${3:-jpg}"
readonly QUALITY=85
# Farben
readonly GREEN='\033[0;32m'
readonly RED='\033[0;31m'
readonly NC='\033[0m'
# ImageMagick prüfen
check_dependencies() {
if ! command -v convert &> /dev/null; then
echo -e "${RED}Fehler: ImageMagick nicht installiert${NC}" >&2
echo "Installieren mit: sudo apt install imagemagick" >&2
exit 1
fi
}
# Bilder konvertieren
convert_images() {
local COUNT=0
local SUCCESS=0
local FAILED=0
# Output-Verzeichnis erstellen
mkdir -p "$OUTPUT_DIR"
# Alle Bilder finden und konvertieren
while IFS= read -r -d '' FILE; do
((COUNT++))
local BASENAME=$(basename "$FILE")
local FILENAME="${BASENAME%.*}"
local OUTPUT="$OUTPUT_DIR/${FILENAME}.$FORMAT"
echo -n "Konvertiere $BASENAME ... "
if convert "$FILE" -quality $QUALITY "$OUTPUT" 2>/dev/null; then
echo -e "${GREEN}OK${NC}"
((SUCCESS++))
else
echo -e "${RED}FEHLER${NC}"
((FAILED++))
fi
done < <(find "$INPUT_DIR" -maxdepth 1 -type f \( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.gif" \) -print0)
# Zusammenfassung
echo ""
echo "=== Zusammenfassung ==="
echo "Gesamt: $COUNT"
echo -e "Erfolgreich: ${GREEN}$SUCCESS${NC}"
echo -e "Fehlgeschlagen: ${RED}$FAILED${NC}"
}
# Main
main() {
echo "=== Batch Image Converter ==="
echo "Input: $INPUT_DIR"
echo "Output: $OUTPUT_DIR"
echo "Format: $FORMAT"
echo "Qualität: $QUALITY"
echo ""
check_dependencies
convert_images
}
main "$@"
Beispiel 5: CSV-Processor
#!/usr/bin/env bash
set -euo pipefail
# CSV-Datei verarbeiten
process_csv() {
local INPUT_FILE="$1"
local TOTAL=0
local COUNT=0
# Header überspringen und Daten verarbeiten
while IFS=',' read -r NAME AGE CITY; do
# Header überspringen
if [[ $NAME == "Name" ]]; then
continue
fi
((COUNT++))
TOTAL=$((TOTAL + AGE))
echo "Person #$COUNT: $NAME, $AGE Jahre, aus $CITY"
done < "$INPUT_FILE"
# Durchschnitt berechnen
if [[ $COUNT -gt 0 ]]; then
local AVG=$((TOTAL / COUNT))
echo ""
echo "Durchschnittsalter: $AVG Jahre"
fi
}
# Main
main() {
local CSV_FILE="${1:-persons.csv}"
if [[ ! -f "$CSV_FILE" ]]; then
echo "CSV-Datei nicht gefunden: $CSV_FILE" >&2
exit 1
fi
process_csv "$CSV_FILE"
}
main "$@"
Beispiel 6: Interactive Menu
#!/usr/bin/env bash
set -euo pipefail
# Menu anzeigen
show_menu() {
clear
echo "========================"
echo " Hauptmenü"
echo "========================"
echo "1. System-Info anzeigen"
echo "2. Backup erstellen"
echo "3. Logs anzeigen"
echo "4. Services verwalten"
echo "5. Beenden"
echo "========================"
}
# System-Info
show_system_info() {
clear
echo "=== System-Information ==="
echo "Hostname: $(hostname)"
echo "Kernel: $(uname -r)"
echo "Uptime: $(uptime -p)"
echo "CPU: $(lscpu | grep "Model name" | cut -d: -f2 | xargs)"
echo "RAM: $(free -h | grep Mem | awk '{print $3 "/" $2}')"
echo ""
read -p "Drücke Enter zum Fortfahren..."
}
# Backup
create_backup_interactive() {
clear
echo "=== Backup erstellen ==="
read -p "Quell-Verzeichnis: " SOURCE
read -p "Ziel-Verzeichnis: " DEST
if [[ -d "$SOURCE" ]]; then
echo "Erstelle Backup..."
tar -czf "$DEST/backup_$(date +%Y%m%d_%H%M%S).tar.gz" "$SOURCE"
echo "Backup erfolgreich!"
else
echo "Fehler: Quelle nicht gefunden"
fi
read -p "Drücke Enter zum Fortfahren..."
}
# Logs anzeigen
show_logs() {
clear
echo "=== System-Logs ==="
echo "1. Letzte 20 Zeilen"
echo "2. Errors"
echo "3. Zurück"
read -p "Auswahl: " CHOICE
case $CHOICE in
1)
journalctl -n 20
;;
2)
journalctl -p err -n 50
;;
3)
return
;;
esac
read -p "Drücke Enter zum Fortfahren..."
}
# Services
manage_services() {
clear
echo "=== Service-Verwaltung ==="
echo "1. Status anzeigen"
echo "2. Service starten"
echo "3. Service stoppen"
echo "4. Zurück"
read -p "Auswahl: " CHOICE
case $CHOICE in
1)
systemctl list-units --type=service --state=running
;;
2)
read -p "Service-Name: " SERVICE
sudo systemctl start "$SERVICE"
echo "Service gestartet"
;;
3)
read -p "Service-Name: " SERVICE
sudo systemctl stop "$SERVICE"
echo "Service gestoppt"
;;
4)
return
;;
esac
read -p "Drücke Enter zum Fortfahren..."
}
# Main Loop
main() {
while true; do
show_menu
read -p "Wähle eine Option: " CHOICE
case $CHOICE in
1)
show_system_info
;;
2)
create_backup_interactive
;;
3)
show_logs
;;
4)
manage_services
;;
5)
echo "Auf Wiedersehen!"
exit 0
;;
*)
echo "Ungültige Auswahl"
sleep 1
;;
esac
done
}
main "$@"
Ende der Bash Scripting Anleitung! 🎉
Diese Anleitung deckt alle wichtigen Aspekte des Bash-Scriptings ab. Für spezifische Anwendungsfälle können die Beispiele als Templates verwendet und angepasst werden.
No comments to display
No comments to display