2.6 Bash

Wenn wir uns über SSH mit dem Server verbinden, landen wir automatisch in der Bourne-again shell (Bash). Die Bash ist der Kommandozeileninterpreter des Terminalfensters unseres Linux-Servers.

Bash steht für "Bourne-again shell" und ist eine verbesserte Version der ursprünglichen Unix-Shell, der Bourne-Shell (sh) von Stephen Bourne. Die Bash wurde 1989 entwickelt, um die Funktionalität der Bourne-Shell zu erweitern und gleichzeitig kompatibel mit dieser zu bleiben. Sie bietet interaktive Kommandozeilenfunktionen und Skripting-Fähigkeiten, die das Automatisieren von Aufgaben und die Verwaltung von Systemen erleichtern. Die Bash ist heute die am weitesten verbreitete Shell in der Unix-Welt und wird auf den meisten Linux-Systemen standardmäßig verwendet.

Die Bash ist also ein Kommandozeileninterpreter, der die vom Benutzer eingegebenen Befehle liest und ausführt. Dieselben Befehle können wir auch in eine Datei schreiben und nacheinander ausführen lassen. Eine solche Datei heißt Bash-Skript. Auf dieser Seite lernen wir Schritt für Schritt die Bausteine kennen und wenden sie in vier kleinen Beispielskripten an, die im Praktikum weiterverwendet werden.

Grundlegende Bash-Konzepte

Einige Bash-Konzepte kennen wir bereits.

  1. Pipelines ermöglichen es, die Ausgabe eines Befehls direkt als Eingabe für einen anderen Befehl zu verwenden, was durch das |-Zeichen erreicht wird.

    Beispiel

    ls -l | grep ".txt"
    

    Dieser Befehl listet alle Dateien im aktuellen Verzeichnis auf und filtert nur die Dateien, die ".txt" im Namen enthalten.

  2. Umleitungen verwenden >, >>, <, 2> und &> Zeichen, um die Standardausgabe, Standardeingabe und Fehlerausgabe umzuleiten.

    Beispiel

    echo "Text in Datei" > datei.txt
    cat datei.txt
    

    Hier wird der Text "Text in Datei" in datei.txt geschrieben, und der Inhalt der Datei wird anschließend mit cat angezeigt.

  3. Job-Kontrolle in der Bash ermöglicht es, Prozesse im Hintergrund (&) oder Vordergrund (fg) laufen zu lassen und laufende Jobs zu verwalten (jobs).

    Beispiel

    sleep 100 &
    jobs
    fg %1
    

    Der Befehl sleep 100 & startet den sleep-Prozess im Hintergrund. Mit jobs werden alle laufenden Jobs angezeigt und fg %1 holt den ersten Job in den Vordergrund. Eine ausführliche Erklärung mit Beispielausgaben steht in 2.5.8 Prozessmanagement.

  4. Aliase sind Kurzbefehle, die längere Befehle oder Befehlskombinationen abkürzen.

    Beispiel

    Hier ein Alias zu einem komplexeren Befehl alias counthtml='find /var/www/html -type f -name "*.html" | wc -l'.

    Wir können nach der Definition in die Kommandozeile einfach counthtml schreiben und damit den gesamten Befehl ausführen. Aber dieser Alias ist nur bis zum nächsten Ausloggen definiert. Um den Alias dauerhaft verwenden zu können, müssen wir die Datei .bashrc editieren, die im Heimatverzeichnis eines jeden Users liegt.

    Hierzu die Konfigurationsdatei öffnen nano ~/.bashrc und ans Ende der Datei den o.g. Alias schreiben.

    Um die neue Konfiguration .bashrc der Bash bekannt zu geben, muss man diese mit source ~/.bashrc aufrufen.

    Nerd-Wissen: man kann mit Hilfe von echo und der richtigen Umleitung einfach den Alias-Befehl ans Ende der .bashrc schreiben. Probieren Sie es aus, aber machen Sie sich vorher ein Backup der Datei.

Variablen

Variablen werden verwendet, um Werte zu speichern, die später im Skript oder in der Kommandozeile verwendet werden können. Variablen werden ohne Leerzeichen um das Gleichheitszeichen definiert.

Beispiel

benutzername="student"
echo "$benutzername"

Hier wird die Variable benutzername auf "student" gesetzt und dann mit echo ausgegeben. Beachten Sie, dass beim Aufrufen der Variablen das $-Zeichen verwendet wird.

Umgebungsvariablen sind spezielle Variablen, die vom System verwendet werden, um Informationen über die Systemumgebung zu speichern. Beispiele sind PATH, HOME und USER.

Beispiel

echo "$PATH"
echo "$HOME"
echo "$USER"

Diese Befehle zeigen die Werte der Umgebungsvariablen PATH, HOME und USER an.

Sehr häufig wird die Ausgabe eines Befehls in einer Variablen gespeichert. Dazu wird der Befehl in $( ) geschrieben.

Beispiel

heute=$(date +%Y-%m-%d)
echo "Heute ist der $heute"

Zuerst wird date +%Y-%m-%d ausgeführt, z. B. mit dem Ergebnis 2026-10-09. Dieses Ergebnis wird in der Variablen heute gespeichert.

Variablen immer in Anführungszeichen

Schreiben Sie Variablen in doppelte Anführungszeichen, also "$datei" statt $datei. Enthält der Wert ein Leerzeichen, z. B. Mein Bericht.txt, würde die Bash ihn sonst als zwei getrennte Wörter behandeln. In einfachen Anführungszeichen ('$datei') wird die Variable dagegen gar nicht ersetzt.

Benutzereingaben

Mit read wird eine Eingabe von der Tastatur in eine Variable gelesen. Das funktioniert direkt in der Kommandozeile und später genauso in Skripten.

Beispiel

echo "Wie ist dein Name?"
read name
echo "Hallo, $name!"

Nach read name wartet die Bash auf eine Eingabe. Was eingetippt und mit der Eingabetaste bestätigt wird, steht danach in der Variablen name. Beachten Sie, dass erst beim Aufrufen der Variablen das $-Zeichen verwendet wird.

Das erste Skript

Skripte sind Dateien, die eine Reihe von Bash-Befehlen enthalten und ausgeführt werden können, um Aufgaben zu automatisieren. Drei Dinge gehören immer dazu:

  1. In der ersten Zeile steht #!/bin/bash (der sogenannte Shebang). Damit weiß das System, dass die Datei von der Bash ausgeführt werden soll.
  2. Die Datei muss ausführbar sein: chmod +x skript.sh.
  3. Aufgerufen wird das Skript mit ./skript.sh. Das ./ bedeutet „im aktuellen Verzeichnis“. Ohne ./ sucht die Bash das Skript nur in den Verzeichnissen aus $PATH.

Zeilen, die mit # beginnen, sind Kommentare. Sie werden nicht ausgeführt und erklären, was das Skript tut. Die erste Zeile #!/bin/bash ist die einzige Ausnahme.

Beispielskript 1: info.sh

Das Skript gibt einen kurzen Bericht über das System aus. Legen Sie die Datei mit nano info.sh an:

#!/bin/bash
# info.sh - gibt einen kurzen Bericht über das System aus

benutzer=$(whoami)
rechner=$(hostname)
datum=$(date '+%d.%m.%Y %H:%M')

echo "Systembericht vom $datum"
echo "Benutzer: $benutzer"
echo "Rechner:  $rechner"
echo "Speicherplatz auf /:"
df -h /

Ausführen:

chmod +x info.sh
./info.sh

Was passiert hier?

  • Die Befehle whoami, hostname und date werden ausgeführt, ihre Ausgaben werden in drei Variablen gespeichert.
  • echo gibt die Variablen zusammen mit festem Text aus.
  • df -h / wird direkt ausgeführt, seine Ausgabe erscheint ohne Umweg über eine Variable.

Fehlersuche mit bash -x

Wenn ein Skript nicht tut, was es soll, hilft der Aufruf bash -x info.sh. Die Bash zeigt dann vor jeder Zeile an, welchen Befehl sie mit welchen Werten ausführt (mit + markiert).

Parameter und Bedingungen

Einem Skript können beim Aufruf Parameter übergeben werden, z. B. ./skript.sh datei.txt ERROR. Im Skript stehen sie in besonderen Variablen:

Variable Bedeutung
$1, $2, … der erste, zweite, … Parameter
$# die Anzahl der übergebenen Parameter
$0 der Name des Skripts selbst
$? der Rückgabewert des letzten Befehls (0 bedeutet „erfolgreich“)

Mit if führt ein Skript Befehle nur aus, wenn eine Bedingung erfüllt ist. Die Bedingung steht in eckigen Klammern, dabei sind die Leerzeichen innerhalb der Klammern wichtig.

#!/bin/bash
if [ "$1" -gt 10 ]; then
    echo "Die Zahl ist größer als 10."
else
    echo "Die Zahl ist 10 oder kleiner."
fi

Häufige Bedingungen:

Bedingung ist wahr, wenn …
[ "$a" -eq "$b" ] die Zahlen gleich sind (-ne ungleich, -gt größer, -lt kleiner, -ge größer gleich, -le kleiner gleich)
[ "$a" = "$b" ] die Texte gleich sind (!= ungleich)
[ -f "$datei" ] die Datei existiert
[ -d "$verz" ] das Verzeichnis existiert
[ ! -f "$datei" ] die Datei nicht existiert (! kehrt jede Bedingung um)

Mit exit wird ein Skript sofort beendet. exit 0 meldet Erfolg, exit 1 einen Fehler.

Beispielskript 2: zaehlen.sh

Das Skript zählt in einer Datei die Zeilen, die einen Suchbegriff enthalten. Datei und Suchbegriff werden als Parameter übergeben.

#!/bin/bash
# zaehlen.sh - zählt in einer Datei die Zeilen mit einem Suchbegriff
# Aufruf: ./zaehlen.sh DATEI SUCHBEGRIFF

if [ $# -ne 2 ]; then
    echo "Aufruf: $0 DATEI SUCHBEGRIFF"
    exit 1
fi

datei="$1"
begriff="$2"

if [ ! -f "$datei" ]; then
    echo "Die Datei $datei gibt es nicht."
    exit 1
fi

anzahl=$(grep -c "$begriff" "$datei")
gesamt=$(wc -l < "$datei")
echo "$anzahl von $gesamt Zeilen enthalten \"$begriff\"."

Beispielaufruf:

./zaehlen.sh /etc/passwd bash

Die Ausgabe sieht dann z. B. so aus: 10 von 31 Zeilen enthalten "bash".

Was passiert hier?

  • Zuerst prüft das Skript, ob genau zwei Parameter übergeben wurden. Wenn nicht, zeigt es an, wie es aufgerufen werden muss, und endet.
  • Die Parameter werden in Variablen mit sprechenden Namen übernommen. Das macht das Skript lesbarer.
  • Danach prüft es, ob die Datei existiert.
  • grep -c zählt die Zeilen mit dem Suchbegriff, wc -l < "$datei" zählt alle Zeilen. Durch das < gibt wc nur die Zahl ohne den Dateinamen aus.

Schleifen und Rechnen

Mit Schleifen werden Befehle mehrfach ausgeführt.

  • Die for-Schleife durchläuft eine Liste von Werten:

    #!/bin/bash
    for i in 1 2 3 4 5; do
        echo "Nummer $i"
    done
    

    Die Liste kann aus Zahlen, Wörtern oder Dateinamen bestehen, z. B. for datei in *.txt; do. Statt 1 2 3 4 5 kann man auch {1..5} schreiben.

  • Die while-Schleife läuft, solange eine Bedingung erfüllt ist:

    #!/bin/bash
    zaehler=1
    while [ "$zaehler" -le 5 ]; do
        echo "Zähler: $zaehler"
        zaehler=$((zaehler + 1))
    done
    

Zum Rechnen schreibt man den Ausdruck in $(( )), z. B. summe=$((summe + anzahl)). Innerhalb der doppelten Klammern darf das $ vor den Variablen entfallen. Die Bash rechnet nur mit ganzen Zahlen.

Beispielskript 3: sortieren.sh

Das Skript räumt ein Verzeichnis auf. Es verschiebt Dateien nach ihrer Endung in Unterverzeichnisse, z. B. alle .jpg-Dateien nach jpg/.

#!/bin/bash
# sortieren.sh - verschiebt Dateien nach ihrer Endung in Unterverzeichnisse
# Aufruf: ./sortieren.sh VERZEICHNIS

verzeichnis="$1"

if [ ! -d "$verzeichnis" ]; then
    echo "Das Verzeichnis $verzeichnis gibt es nicht."
    exit 1
fi

cd "$verzeichnis"
summe=0

for endung in jpg txt md; do
    anzahl=$(ls *."$endung" 2>/dev/null | wc -l)
    if [ "$anzahl" -gt 0 ]; then
        mkdir -p "$endung"
        mv *."$endung" "$endung"/
        echo "$anzahl Dateien mit der Endung .$endung nach $endung/ verschoben"
        summe=$((summe + anzahl))
    fi
done

echo "Insgesamt $summe Dateien verschoben."

Was passiert hier?

  • Die for-Schleife läuft dreimal, die Variable endung hat nacheinander die Werte jpg, txt und md.
  • ls *."$endung" | wc -l zählt die passenden Dateien. 2>/dev/null verwirft die Fehlermeldung von ls, falls es keine solche Datei gibt. /dev/null ist eine Art „Papierkorb“ für Ausgaben.
  • Nur wenn es Dateien gibt, wird das Unterverzeichnis angelegt und werden die Dateien verschoben.
  • summe zählt mit, wie viele Dateien insgesamt verschoben wurden.
  • Das cd im Skript wirkt nur innerhalb des Skripts. Nach dem Ende befinden Sie sich wieder im selben Verzeichnis wie vorher.

Funktionen

Funktionen fassen mehrere Befehle unter einem Namen zusammen, der dann wie ein Befehl aufgerufen werden kann. Das lohnt sich, wenn dieselben Befehle an mehreren Stellen eines Skripts gebraucht werden.

#!/bin/bash
meine_funktion() {
    echo "Dies ist eine Funktion."
}

meine_funktion  # Ruft die Funktion auf

Bash-Skripte und Crontab

Eine wichtige Eigenschaft von Bash-Skripten ist, dass man sie mit crontab zeitgesteuert ausführen lassen kann (siehe 2.5.8). Ein typisches Beispiel ist ein Backup.

Beispielskript 4: backup.sh

Das Skript sichert ein Verzeichnis als komprimiertes Archiv mit Datum und Uhrzeit im Namen.

#!/bin/bash
# backup.sh - sichert ein Verzeichnis als komprimiertes Archiv
# Aufruf: ./backup.sh VERZEICHNIS

quelle="$1"
ziel="$HOME/backup"
archiv="$ziel/backup_$(date +%Y%m%d_%H%M).tar.gz"

if [ ! -d "$quelle" ]; then
    echo "Fehler: $quelle ist kein Verzeichnis."
    exit 1
fi

mkdir -p "$ziel"
tar -czf "$archiv" "$quelle" 2>/dev/null

if [ $? -eq 0 ]; then
    echo "Backup erstellt: $archiv"
    du -h "$archiv"
else
    echo "Fehler beim Erstellen des Backups."
    exit 1
fi

Was passiert hier?

  • Der Name des Archivs enthält Datum und Uhrzeit, z. B. backup_20261009_0200.tar.gz. So überschreibt ein neues Backup kein älteres.
  • mkdir -p legt das Zielverzeichnis an, falls es noch nicht existiert.
  • $? enthält den Rückgabewert von tar. Ist er 0, war das Archivieren erfolgreich.
  • du -h zeigt zum Schluss die Größe des Archivs an.

Um das Skript jeden Tag um 2 Uhr morgens für das Verzeichnis /var/www/html auszuführen, öffnen wir den Crontab-Editor mit crontab -e und fügen folgende Zeile hinzu:

0 2 * * * /root/backup.sh /var/www/html
Nerd-Wissen Cron-Scripting

Die Zeile 0 2 * * * /root/backup.sh /var/www/html im crontab-Editor bedeutet, dass das Skript backup.sh jeden Tag um 2 Uhr morgens ausgeführt wird.

  • 0: Minute (0. Minute der Stunde)
  • 2: Stunde (2. Stunde des Tages, also 2 Uhr morgens)
  • *: Tag des Monats (jeder Tag des Monats)
  • *: Monat (jeder Monat)
  • *: Wochentag (jeder Wochentag)

Das Skript /root/backup.sh wird somit täglich um 2:00 Uhr morgens ausgeführt. Im Crontab-Eintrag muss der vollständige Pfad zum Skript stehen, da Cron nicht im Heimatverzeichnis startet.