Linux Systeme

Einfache Bash - Scripts

Die Arbeit in der Konsole bietet unter anderem durch die Möglichkeit von Scripts eine komfortable Möglichkeit wiederkehrende Aufgaben zu automatisieren. Dabei wird eine Datei erstellt, die eine Reihe von Befehlen enthält, die sequenziell ausgeführt werden.

Hier sei nochmals auf den Unterschied zwischen den Begriffen Shell und Bash hingewiesen. Eine Shell ist ein Programm, welches uns eine Kommandozeileninterface zur Verfügung stellt. Die Bash ( Bourne Again SHell) ist eine der am weitesten verbreiteten Shells unter Linux.
Die Beispiele und Erklärungen hier gehen von der Verwendung der Bash als Shell aus. Sie können dies in der Konsole mit dem Befehl
echo $SHELL prüfen.

Was genau ist ein Shell-Script?

Ein Shell-Script ist eine ganz normale ASCII-Datei (also eine Text-Datei) die Zeile für Zeile Befehle enthält. Wird dieses Script ausgeführt werden die Befehle sequenziell (Zeile für Zeile von oben nach unten) abgearbeitet.
Sie können Shell-Scripts mit jedem beliebigen Editor erstellen. Wir verwenden aus Übungungszwecken aber natürlich vi.

Hello World!

Unser erstes Script ist - wie könnte es anders sein - helloworld.sh.
Erstellen Sie diese Datei in ihrem Home-Verzeichnis in einem Unterverzeichnis namens ~/bin. Die Datei hat folgenden Inhalt:

#!/bin/bash
HEUTE=$(date)
echo "Hello World!"
echo "Heute ist $HEUTE"
					

Um dieses Script ausführen zu können, müssen wir die Berechtigung noch korrekt setzten. Scripts benötigen das Recht eXecute. Setzen Sie dieses mit dem Befehl chmod.
Sie können das Script nun jederzeit durch Eingabe des Scriptnamens "als Befehl" ausführen. Das funktioniert deshalb, weil wir das Script im Verzeichnis ~/bin abgespeichert haben und dieses Verzeichnis standardmäßig neben den üblichen Systempfaden verwendet wird um nach Befehlen (und Scripts) zu suchen.
Die Ausgabe sollte in etwa so aussehen:

your-login@your-host:~$ helloworld.sh 
Hello World!
Heute ist Mon, 30 Mar 26 14:48:54 +0200

Das Script, Zeile für Zeile:

Zeile #1: #!/bin/bash ist der so genannte shebang. Jedes Script, das mit einem Interpreter gestartet werden muss, wird mit einem shebang gestartet.
Dadurch "weiß" die Shell welches Programm für die Interpretation der Befehle genutzt werden muss.

Zeile #2: HEUTE=$(date)
Es wird eine Variable mit dem Namen HEUTE definiert. Der Wert der Variable wird durch die Ausgabe eines Befehls "befüllt".
Dies wird dadurch erreicht, indem Sie die Schreibweise $( ... ) verwenden. Alles was zwischen den Klammern steht, wird als Befehl gewertet und ausgeführt. Die Ausgabe, die dieser Befehl liefert wird anschließend in die Variable gespeichert.

Zeile #3: echo "Hello World!"
Diese Zeile funktioniert exakt so, wie sie auch bei der normalen Eingabe in der Konsole funktioniert. Es wird der Text zwischen den " ausgegeben.

Zeile #4: echo "Heute ist $HEUTE"
Ähnliche Funktion wie in Zeile 3. Nur geben wir hier den Inhalt der Variablen $HEUTE im Text aus.

Aufgabe 1
  • Mit dem Befehl read können Sie Benutzereingaben in eine Variable einlesen. Verwenden sie die Kurzhilfe des Befehls um sich mit der Syntax vertraut zu machen.
    Erweitern Sie nun das helloworld.sh Script um eine Eingabe für einen Namen. Begrüßen Sie anschließend im Script den Benutzer mit seinem Namen.
  • Die Ausgabe des Befehls date kann durch Übergabe eines Formatstrings auch beliebig formatiert werden.
    Formatieren Sie die Ausgabe so, dass nur noch der Wochentag, der Tag im Monat, das Monat die Stunde und die Minuten ausgegeben werden.
  • Wie können Sie das Script in der Konsole so aufrufen, dass Sie nicht mehr den Namen eingeben müssen sondern sie diesen bereits vorher beim in der Befehlszeile definieren (Stichwort Piping)?
  • Dokumentieren Sie den den aktuellen Stand des Quellcodes
Mögliche Lösung
helloworld.sh

Wie lautet dein Name? Joachim
Hallo Joachim!
Heute ist Mittwoch, 21. August, 11:23
echo DummyUser | helloworld.sh 

Hallo DummyUser!
Heute ist Mittwoch, 21. August, 11:24
cat ~/bin/helloworld.sh
#!/bin/bash
HEUTE=$(date +"%A, %d. %B, %H:%M")
read -p "Wie lautet dein Name? " NAME
echo "Hallo $NAME!"
echo "Heute ist $HEUTE"
	

Kommentare, Variablen und Bedingungen

Bash-Scripting ist hervorragend dafür geeignet einfache Aufgaben zu automatisieren. Ein Bash-Script ist dann die beste Wahl wenn Sie die Befehle die abgearbeitet werden sollen auch manuell in die Konsole eingeben könnten oder müssten. Diese Scripts unterstützen auch einige Konzepte die man aus gängigen Programmiersprachen gewohnt ist wie Bedinungen, Schleifen, Variablen usw.
Ist Ihre Anforderung jedoch komplexer ist vermutlich eine andere Programmiersprache besser dafür geeignet.

Wie in jedem Quellcode ist es auch bei Scripts von Vorteil wenn man die Funktionsweise kommentiert. Auch hier gilt (wie in allen Programmiersprachen): kommentieren Sie keine offensichtlichen Programmblöcke sondern verwenden Sie Kommentare dort, wo sie einen Mehrwert haben.
Als Kommentarzeichen wird im Bash-Scripting # verwendet. Alles was in einer Zeile nach dem # kommt wird daher als Kommentar betrachtet.

Weiter oben haben wir bereits eine Variable (HEUTE) verwendet. Folgende Punkte sind bei der Verwendung von Variablen zu beachten:
- Variablenbezeichnungen dürfen nur aus Buchstaben, Zahlen und _ bestehen
- Das erste Zeichen muss ein Buchstabe oder _ sein
- Es dürfen keine Sonder- oder Leerzeichen verwendet werden
- Groß- und Kleinschreibung wird unterschieden (case-sensitive)
- Bei der Zuweisung verwendet man nur den Variablennamen - ohne $
- Bei der Verwendung der Variable wird ein $ vor den Variablennamen notiert.

Bedingungen werden mit dem if Statement definiert. Die Syntax lautet

if [ condition ];
then
	statement
elif [ condition ]; then
	statement 
else
	do this by default
fi
ACHTUNG - achten Sie sorgfälltig auf die Leerzeichen bei den Bedingungen und auf den ; nach der Bedingung!

Definition von Bedingungen
Bedigungen werden in eckigen Klammern [] geschrieben. Nach der einleitenden Klammer und vor der abschließenden Klammer muss ein Leerzeichen stehen. Nach der abschließenden Klammer muß ein ; stehen.
Die Bedingungen kennen boolsche Vergleiche:
= Vergleicht 2 Werte (Texte) miteinander. True, wenn der erste Wert gleich dem zweiten Wert ist. if [ "$NAME" = "admin" ];
!= Vergleicht 2 Werte miteinander. True, wenn der erste Wert NICHT gleich dem zweiten Wert ist. if [ "$NAME" != "" ];
-eq eq = Equal; Vergleicht 2 Ganzzahlen miteinander. True, wenn der erste Wert gleich dem zweiten Wert ist. if [ $NR -eq 2 ];
-ne ne = Not equal; Vergleicht 2 Ganzzahlen miteinander. True, wenn der erste Wert NICHT gleich dem zweiten Wert ist. if [ $NE -ne 10 ];
-lt lt = less than; True, wenn der erste Wert kleiner als der zweite Wert ist. if [ $INDEX -lt 10 ];
-gt gt = greater than; True, wenn der erste Wert größer als der zweite Wert ist. if [ $INDEX -gt 5 ];
-a a = and; Verknüpft zwei Bedingungen mit UND if [ $a -gt 60 -a $a -lt 100 ];
-o o = or; Verknüpft zwei Bedingungen mit ODER if [ $NAME -eq "admin" -o $NAME -eq "root" ];

Aufgabe 2
  • Erweitern Sie das Script um sinnvolle Kommentare.
    Es muss jedenfalls eine Versions-Nummer mit dem Datum der letzten Änderung und eine Kurzbeschreibung geben.
  • Erweitern Sie das Script um die Eingabemöglichkeit der gewünschten Ausgabesprache.
    Möglich sollen die Eingaben de und en sein. Standard (wenn der Benutzer nichts eingibt) sollte de sein.
    Sollte der Benutzer einen anderen Text als de oder en eingeben, geben Sie bitte eine Fehlermeldung aus und beenden das Script sofort mit dem Befehl exit.
  • Dokumentieren Sie den den aktuellen Stand des Quellcodes
Mögliche Lösung
cat ~/bin/helloworld.sh
#!/bin/bash
# helloworld.sh, Version 2.0, 21.08.2024
# -----------------------------------------------
# Einfaches Shellscript um verschiedene Grundkonzepte wie Variablen, User Ein- und Ausgaben
# und dergleichen einzusetzen.
#
# Version 1.0 um Kommentare und Sprachauswahl ergänzt
#

# Einlesen des aktuellen Datums im geforderten Format:
# %A = Wochentag
# %d = Tag im Monat
# %B = Monat
# %H = Stunde im 24-Stundenformat
# %M = Minuten
HEUTE=$(date +"%A, %d. %B, %H:%M")

# Auswahlmöglichkeit der Sprache
read -p "Wählen Sie ihre bevorzugte Sprache / Please choose your preferred language [de|en] " LANG
# Standard wäre de, wenn nichts eingegeben wurde
if [ -z "$LANG" ]; then
	LANG="de"
fi

if [ "$LANG" != "de" ] && [ "$LANG" != "en" ]; then
	echo "Ungültige Sprachauswahl. de für deutsch oder en für englisch"
	echo "Invalid language. de to use german, en to use english"
	exit 1
fi

if [ "$LANG" == "de" ]; then
	PROMPTNAME="Wie lautet dein Name?"
	HELLOSTR="Hallo"
	TODAYSTR="Heute ist"
fi
if [ "$LANG" == "en" ]; then
	PROMPTNAME="What's your name?"
	HELLOSTR="Hello"
	TODAYSTR="Today is"
fi

# Einlesen eines Namens für die anschließende Begrüßung
read -p "$PROMPTNAME " NAME

echo "$HELLOSTR $NAME!"
echo "$TODAYSTR $HEUTE"
	

Schleifen

Bash-Scripts unterstützen auch for und while Schleifen. Diese funktionieren gleich wie aus anderen Programmiersprachen gewohnt.
Syntax einer while Schleife
i=1
while [ $i -le 10 ] ; do
   echo "$i"
  (( i += 1 ))
done
Syntax einer for Schleife
for i in {1..5}
do
    echo $i
done
In diesem Beispiel wird die Schleife aus einer numerischen Liste zwischen 1 und 5 generiert.
Es können aber beispielsweise auch Strings für die Schleife verwendet werden:
USERS="root nobody someuser"
for i in $USERS
do
    echo "Prüfe etwas bezüglich des Users $i"
done
Die Ausgabe eines Befehls kann ebenso dafür verwendet werden
i = 1
for LINE in $(head /etc/passwd)
do
    echo "Zeile $i in /etc/passwd: $LINE"
    (( i += 1 ))
done
Oder auch Dateinamen aufgrund eines Ausdrucks
i = 1
for FILE in /etc/*.conf
do
    echo "Datei mit Endung .conf in /etc: $FILE"
done
Aufgabe 3
  • Erstellen Sie eine neue Script-Datei mit dem namen loops.sh im Unterverzeichnis bin ihres Homeverzeichnisses.
  • Erstellen Sie eine Schleife die bei jedem Durchlauf den Benutzer nach der Eingabe eines Benutzernamens fragt. Überprüfen Sie anschließend ob es einen Benutzer mit diesem Namen auf ihrem System gibt. Geben Sie eine entsprechende Meldung aus.
  • Die Schleife soll abgebrochen werden, wenn der Benutzer keinen Text eingibt sondern nur ENTER drückt
  • Erstellen Sie einen Countdown
    Fragen Sie dabei zuerst per Eingabe den Startwert ab. Dekrementieren Sie bei jedem Durchlauf die Variable
  • Erstellen Sie eine Schleife welche durch 3 Namen iteriert. Mit jedem dieser Namen soll das Script helloworld.sh von oben ausgeführt werden.
  • Versehen Sie das Script mit sinnvollen Kommentaren.
  • Dokumentieren Sie den Quellcode des Scripts
Mögliche Lösung
cat ~/bin/loops.sh
#!/bin/bash
# loops.sh, Version 1.0, 21.08.2024
# -----------------------------------------------
# Einfaches Shellscript um die Konzepte der verschiedenen
# Schleifen ausprobieren zu können
#

## Endlosschleife mit der Möglichkeit über den Befehl "id" zu prüfen
# ob ein eingegebener User im System angelegt ist
# Schleife wird beendet, wenn kein Username eingegeben wurde
while true; do
	read -p "Username [Leer = Abbruch]: " USER
	if [ "$USER" == "" ]; then break; fi
	if id $USER > /dev/null 2>&1; then
		echo "User $USER gefunden"
	else
		echo "User $USER nicht gefunden"
	fi
done

## Countdown mit individueller Eingabe durch den Benutzer
read -p "Geben Sie den Startwert für den Countdown ein: " COUNTDOWN

if ! [[ "$COUNTDOWN" =~ ^[0-9]+$ ]]; then
	echo "Ungültige Eingabe. Bitte nur Zahlen"
	exit 1
fi

echo "Der Countdown beginnt bei $COUNTDOWN"
while [ $COUNTDOWN -ge 0 ]; do
	echo $COUNTDOWN
	((COUNTDOWN--))
done
echo "Houston, we have a lift-off!"

## Iteration durch 3 Namen mit denen helloworld.sh aufgerufen wird
NAMES="Gandalf Frodo Bilbo"
LANG="de"
for N in $NAMES; do
	# Das Script helloworld.sh erwartet zwei Eingaben. 
	# 1: Sprache (de|en)
	# 2: Name
	# Daher werden beide Werte mit echo ausgegeben. Die Option -e sorgt dafür
	#   dass Sonderzeichen wie ein Zeilenumbruch mit \n interpretiert werden.
	echo -e "$LANG\n$N" | helloworld.sh	
done

	

Tests als Bedingungen

Weiter oben wurden in den Bedingungen bereits einige Möglichkeiten vorgestellt wie wir ein IF-Konstrukt definieren können.
Die Möglichkeiten dazu sind jedoch noch vielfältiger. Implizit wird bei einem if der Befehl test verwendet. Diesen können wir auch beliebig in der Konsole verwenden.
Wichtig dabei zu beachten ist, dass der Befehl keine Ausgabe generiert sondern nur mit sogenannten Exit-Codes arbeitet. Ist ein Test erfolgreich liefert test als Exit-Code 0, ansonsten einen Wert > 0
In der Arbeit direkt in der Konsole können wir daher mit Erfolgs- und Fehlerabhängigen Verknüpfungen arbeiten
test -e /pfad/gibt-es-nicht || echo "Den Pfad gibt es nicht!"
Wenn wir diese Tests in einem if Statement verwenden können wir auf den Befehl test verzichten, die Bedingung prüft dabei den Exit-Code
# Wird ein ! vor den Test gestellt, wird die Überprüfung negiert
if [ ! -e /pfad/gibt-es-nicht ]; then
    echo "Den Pfad gibt es nicht!"
fi
Aufgabe 4
  • Öffnen Sie das Benutzerhandbuch (manual page) zum Befehl test mit man test und machen Sie sich mit den möglichen Tests vertraut.
  • Erstellen Sie eine neue Script-Datei mit dem namen tests.sh im Unterverzeichnis bin ihres Homeverzeichnisses.
  • Überprüfen Sie im Script ob es in Ihrem Home-Verzeichnis ein Unterverzeichnis mit dem Namen demo-conditions gibt. Wenn nicht, soll das Script dieses Verzeichnis erstellen.
  • Erstellen Sie eine Schleife welche über alle Dateien im Verzeichnis /etc iteriert.
  • Prüfen Sie für jede Datei ob der Benutzer Leseberechtigungen hat.
    Darf die Datei nicht gelesen werden, geben Sie eine Information darüber aus
  • Erweiteren Sie das Script indem Sie zwei Zähler mitführen. Für jede Datei die gelesen werden darf, erhöht sich der Erfolgs- und für jede Verweigerte der Fehlerzähler.
    Nach Beendigung der Schleife geben Sie aus, wie viele Dateien gelesen werden durften und wie viele nicht.
  • Versehen Sie das Script mit sinnvollen Kommentaren.
  • Dokumentieren Sie den Quellcode des Scripts
Mögliche Lösung
cat ~/bin/tests.sh
#!/bin/bash
# tests.sh, Version 1.0, 21.08.2024
# -----------------------------------------------
# Einfaches Shellscript um die Konzepte der verschiedenen
# tests wie auf vorhandensein, Berechtigung usw.
# anzuwenden
#

## Prüfen, ob ein Verzeichnis vorhanden ist und wenn nicht, erstellen.
DIR="$HOME/demo-condition"
if ! [ -d "$DIR" ]; then	
	echo "Verzeichnis $DIR noch nicht vorhanden. Es wird erstellt."
	mkdir $DIR || exit 1
fi


## Prüfen der Leseberechtigung auf alle Dateien im Verzeichnis /etc
rTrue=0
rFalse=0
for FILE in /etc/*
do
	if [ -r $FILE ]; then
		(( rTrue += 1 ))
	else
		echo "Kein Lesezugriff auf Datei $FILE"
		(( rFalse += 1 ))
	fi
done

echo "Lesezugriff gewährt: $rTrue; verweigert: $rFalse"

	

Übungsbeispiel Backup-Script

Aufgabe 5
  • Erstellen Sie ein Script, dass bei Aufruf eine Sicherung des Verzeichnisses ~/example-data macht.
  • Um ein Verzeichnis sichern zu können kann man beispielsweise den Befehl tar verwenden. Machen Sie sich mit der Funktionsweise des Befehls vertraut. Testen sie zuerst in der Konsole welche Parameter sie benötigen um ein Verzeichnis in ein Archiv zu schreiben und welche um dieses Archiv wieder zu extrahieren.
  • Die Sicherungsdatei muss im Verzeichnis ~/backup abgelegt sein. Das Script muss prüfen, ob dieses Verzeichnis vorhanden ist. Wenn nicht, wird es vom Script angelegt.
  • Der Dateiname muss das aktuelle Datum in der Form YYYYMMDD beinhalten.
  • Die Sicherungsdatei muss komprimiert/gepackt sein. Dies können Sie mit tar schon während des Erstellens erreichen oder Sie verwenden dazu ein separates Tool wie xz, gzip oder bzip2.
  • Das Script muss prüfen, ob bereits eine Sicherungsdatei vom selben Tag vorhanden ist. Wenn ja, soll der User entscheiden ob die alte Sicherung gelöscht, oder der Vorgang abgebrochen werden soll.
  • Am Ende soll das Script mit Hilfe des Befehls logger eine Meldung an das System-Protokoll senden, dass die Sicherung erfolgreich abgeschlossen wurde. Der Dateiname soll in der Meldung ebenfalls vorkommen.
    Tipp: Testen Sie auch logger vorab in der Konsole mit einem beliebigen Text. Auslesen können Sie dann beispielsweise die letzten 10 Einträge in Ihrem User-Protokoll mit journalctl --user --no-pager -n 10.
  • Versehen Sie das Script mit sinnvollen Kommentaren.
  • Dokumentieren Sie den Quellcode des Scripts
Mögliche Lösung
cat ~/bin/backup.sh
#!/bin/bash
# backup.sh, Version 1.0, 21.08.2024
# -----------------------------------------------
# Ein Shellscript welches eine tägliche Sicherung eines speziellen
# Verzeichnisses macht. 
#

# Definieren der Variablen
SOURCE="$HOME/example-data"
TARGET="$HOME/backup"
D=$(date +"%Y%m%d")
FILE="example-data-$D.tar.gz"
BACKUPPATH="$TARGET/$FILE"

# Prüfen, ob es das zu sichernde Verzeichnis überhaupt gibt
if ! [ -d $SOURCE ]; then
	echo "Das zu sichernde Verzeichnis $SOURCE ist nicht vorhanden"
	exit 1
fi

# Zielverzeichnis erstellen, falls nicht vorhanden
if ! [ -d $TARGET ]; then
	mkdir $TARGET || exit 2
fi

# Prüfen, ob es für diesen Tab bereits eine Sicherung gibt
if [ -e $BACKUPPATH ]; then
	read -p "Die Sicherung $FILE ist bereits vorhanden. Neu erstellen? [y|n]: " REMOVE
	if [ "$REMOVE" != "y" ]; then
		exit 0
	fi
	rm $BACKUPPATH || exit 3
fi

# Sicherung erstellen
tar -czf $BACKUPPATH $SOURCE

# Prüfen auf den Rückgabewert des tar Befehls. Dieser ist 0, wenn der Befehl erfolgreich war
if [ $? -ne 0 ]; then
	MSG="Sicherung $FILE fehlgeschlagen"
	echo $MSG
	logger $MSG
	exit 100
fi

# Protokollierung
MSG="Sicherung $FILE erfolgreich"
echo $MSG
logger $MSG


	
UPLOAD
Laden Sie bitte das Dokument über MS-Teams in den Bereich "Aufgaben" zur Aufgabe "Linux: Übungsbeispiele Bash-Scripts" hoch.