Zum Inhalt springen

Das erste Skript

Jedes neue Skript beginnt mit denselben Kopfzeilen, dann chmod +x, dann den Editor öffnen. Das ist Tipparbeit, die sich automatisieren lässt. Genau das macht addscript.sh, das du auf dieser Seite in kleinen Schritten baust. Nebenbei lernst du alles kennen, was ein Skript ausmacht.

Ein Shell-Skript ist eine Textdatei mit Befehlen. Die Shell liest sie von oben nach unten und führt Zeile für Zeile aus, als hättest du sie getippt. Drei Dinge braucht es, damit das klappt:

  1. Die erste Zeile, der Shebang, sagt dem System, welcher Interpreter das Skript ausführen soll:
    hallo.sh
    #!/bin/bash
    echo "Hallo $(whoami), heute ist $(date +%A)."
  2. Die Datei muss ausführbar sein:
    Terminal-Fenster
    chmod +x hallo.sh
  3. Der Aufruf braucht einen Pfad, weil die Shell Programme nur in den Verzeichnissen aus $PATH sucht und das aktuelle Verzeichnis nicht dazugehört:
    Terminal-Fenster
    ./hallo.sh

Ohne x-Bit geht es auch mit bash hallo.sh, dann ist der Shebang egal. Üblich ist aber der Aufruf mit ./.

Skripte in diesem Kurs beginnen alle mit demselben Kopf. Er beantwortet die Fragen, die man sich drei Monate später stellt: Was macht das Ding, wer hat es geschrieben, wann?

Kopfzeilen-Konvention
#!/bin/bash
#
# skriptname.sh
# Zweck des Skripts, gerne auch über mehrere Zeilen
#
# 2026-11-05 / Vorname Nachname
#
# Variablen
# Funktionen
# Hauptprogramm

Die drei Abschnitte halten Ordnung: zuerst alle Variablen, dann die Funktionen (ab Teil 3), dann der eigentliche Ablauf. Die Details stehen in der Skript-Konvention.

Das Skript soll ein neues Skript mit genau diesem Kopf anlegen, es ausführbar machen und im Editor öffnen. Als Flowchart:

flowchart TD
    S(["Start"]) --> W["Template in Datei schreiben"]
    W --> X["Datei ausführbar machen"]
    X --> E["Editor öffnen"]
    E --> Z(["Ende"])

Ovale sind Start und Ende, Rechtecke sind Operationen. Später kommen Rauten für Verzweigungen dazu. Flowcharts zeichnest du in diesem Kurs als Mermaid, eine Textnotation, die GitLab und diese Webseite direkt als Diagramm anzeigen. Den Quelltext des Diagramms oben findest du in der Lösung von Aufgabe 8.2.

Wechsle in dein Kurs-Repository, lege labs/linux/08-skripte/ an und erstelle darin addscript.sh:

addscript.sh
#!/bin/bash
#
# addscript.sh
# Erstellt ein Skript aus dem Kurs-Template, macht es
# ausführbar und öffnet es im Editor.
#
# 2026-11-05 / Vorname Nachname
#
# Hauptprogramm
# Template mit echo ausgeben und in die Datei umleiten
echo "#!/bin/bash
#
# newscript.sh
# Zweck des Skripts
#
# Datum / Name
#
# Variablen
# Funktionen
# Hauptprogramm" > newscript.sh
# Ausführbar machen und im Editor öffnen
chmod +x newscript.sh
vim newscript.sh

Ein echo mit mehrzeiligem Text in Anführungszeichen, dessen Ausgabe mit > in eine Datei umgeleitet wird. Danach chmod und der Editor. Wer lieber nano oder code verwendet, ersetzt vim. Trage dein Datum und deinen Namen ein.

Testen. Mach das Skript ausführbar und überlege, bevor du es startest: Was sollte passieren? Woran erkennst du nachher, dass es funktioniert hat?

Terminal-Fenster
cd labs/linux/08-skripte
chmod +x addscript.sh
./addscript.sh
ls -l
cat newscript.sh

Das Skript ist starr: Es erzeugt immer newscript.sh und überschreibt eine vorhandene Datei ohne Warnung. Erster Schritt zur Besserung: Der Dateiname kommt in eine Variable. Markiert sind die Zeilen, die neu sind oder sich ändern.

addscript.sh
#!/bin/bash
#
# addscript.sh
# Erstellt ein Skript aus dem Kurs-Template, macht es
# ausführbar und öffnet es im Editor.
#
# 2026-11-05 / Vorname Nachname
#
# Variablen
dateiname="test.sh"
# Hauptprogramm
# Template mit echo ausgeben und in die Datei umleiten
echo "#!/bin/bash
#
# $dateiname
# Zweck des Skripts
#
# Datum / Name
#
# Variablen
# Funktionen
# Hauptprogramm" > "$dateiname"
# Ausführbar machen und im Editor öffnen
chmod +x "$dateiname"
vim "$dateiname"

Eine Variable hat einen Namen und einen Wert. Zugewiesen wird mit = ohne Leerzeichen daneben, gelesen mit $dateiname. Variablen stehen beim Lesen immer in doppelten Anführungszeichen: So bleibt ein Wert mit Leerzeichen ein Wert, und ShellCheck ist zufrieden. Innerhalb von "..." werden Variablen ersetzt, innerhalb von '...' nicht.

Richtig, das Ganze bringt noch wenig. Kommt gleich. Testen trotzdem, der Code hat sich geändert: Was erwartest du?

Richtig flexibel wird addscript.sh, wenn der Dateiname beim Aufruf mitgegeben wird: ./addscript.sh backup.sh. Dafür stellt die Bash spezielle Variablen bereit, die sie beim Start automatisch füllt. Schau sie dir mit einem kleinen Demo-Skript an:

argumente.sh
#!/bin/bash
#
# argumente.sh
# Zeigt, wie die Bash Argumente an ein Skript übergibt.
#
# 2026-11-05 / Vorname Nachname
#
# Hauptprogramm
echo "Der Skriptname: $0"
echo "Anzahl Argumente: $#"
echo "Alle Argumente: $*"
echo "Das 1. Argument: $1"
echo "Das 2. Argument: $2"
echo "Das 3. Argument: $3"
Terminal-Fenster
chmod +x argumente.sh
./argumente.sh so geht das
./argumente.sh "so geht" das

Was erwartest du beim zweiten Aufruf? Die Anführungszeichen fassen zwei Wörter zu einem Argument zusammen.

Variable Inhalt
$0 Name des Skripts, so wie es aufgerufen wurde
$1 bis $9 erstes bis neuntes Argument, ab dem zehnten ${10}
$# Anzahl der Argumente
$* alle Argumente als ein String
"$@" alle Argumente, jedes einzeln (für Schleifen, nächste Seite)
$? Exit-Status des zuletzt ausgeführten Befehls

In addscript.sh ist nur eine Zeile zu ändern. Die feste Zuweisung fliegt raus, das erste Argument kommt rein:

addscript.sh
# Variablen
dateiname="test.sh"
dateiname="$1"

Testen. Was erwartest du?

Terminal-Fenster
./addscript.sh neu.sh
./addscript.sh auch.sh
ls -l

Die schöne Änderung hat ein Loch. Hast du es bemerkt? Ruf das Skript ohne Argument auf:

Terminal-Fenster
./addscript.sh

Die Shell meldet No such file or directory, chmod beschwert sich, und der Editor öffnet irgendetwas, nur nicht das, was du wolltest. Nicht gerade ein Verhalten, das gute Software auszeichnet. Das Skript muss seine Argumente prüfen und bei Bedarf abbrechen. Dazu braucht es Bedingungen.

Eine Verzweigung sieht in der Bash so aus:

Terminal-Fenster
if [[ bedingung ]]; then
befehle # wenn die Bedingung wahr ist
else
befehle # sonst
fi

Der else-Teil ist optional. Die Bedingung steht in doppelten eckigen Klammern. Historisch ist [ ein eigenes Programm (test), [[ ]] ist die eingebaute, bequemere Bash-Variante: Sie erlaubt && und || innerhalb der Klammern und kommt mit leeren Variablen besser zurecht. In Bash-Skripten verwenden wir immer [[ ]]. Die wichtigsten Tests:

Test wahr, wenn
-e datei die Datei existiert (egal welcher Typ)
-f datei es eine normale Datei ist
-d pfad es ein Verzeichnis ist
-r, -w, -x datei lesbar, schreibbar, ausführbar
-z "$s" der String leer ist
-n "$s" der String nicht leer ist
"$a" == "$b", "$a" != "$b" Strings gleich, ungleich
$x -eq $y Zahlen gleich, dazu -ne, -lt, -le, -gt, -ge
! bedingung die Bedingung nicht wahr ist

Jede Bedingung ist ein Befehl mit einem Exit-Status: 0 heisst wahr beziehungsweise erfolgreich, alles andere falsch beziehungsweise Fehler. Dasselbe gilt für dein ganzes Skript. Mit exit 0 endet es regulär, mit exit 1 (oder einer anderen Zahl ungleich 0) meldet es einen Fehler. Wer das Skript aufruft, sieht den Wert in $? und kann darauf reagieren, etwa mit ./addscript.sh x.sh && echo "ok".

Probiere das Demo-Skript scripts/linux/08-skripte/testdemo.sh aus dem Repository aus, es führt alle Tests einmal vor.

Für addscript.sh heisst das: Fehlt das Argument, Hinweis ausgeben und mit exit 1 abbrechen. Im Flowchart kommt eine Raute dazu, und es gibt neu zwei Enden:

flowchart TD
    S(["Start"]) --> P{"Argument vorhanden?"}
    P -->|nein| M["Benutzung ausgeben"] --> E1(["Ende, exit 1"])
    P -->|ja| W["Template in Datei schreiben"]
    W --> X["Datei ausführbar machen"]
    X --> ED["Editor öffnen"]
    ED --> E0(["Ende, exit 0"])
addscript.sh
# Variablen
dateiname="$1"
benutzung="Benutzung: $0 skriptname.sh"
# Hauptprogramm
# Aufruf prüfen, im Fehlerfall Hinweis ausgeben und abbrechen
if [[ $# -lt 1 ]]; then
echo "$benutzung"
exit 1
fi

Auf Deutsch: Wenn die Anzahl Argumente kleiner als 1 ist, dann Benutzung ausgeben und mit Status 1 abbrechen. Der Block gehört an den Anfang des Hauptprogramms, vor das echo mit dem Template.

Testen mit null, einem und zwei Argumenten. Prüfe jeweils den Exit-Status:

Terminal-Fenster
./addscript.sh
echo $?
./addscript.sh zwei.sh
echo $?

Die Felder «Datum / Name» im Kopf soll addscript.sh selbst ausfüllen. Das Datum liefert date, mit einem Format-String auf Tag.Monat.Jahr reduziert, den Login-Namen whoami:

Terminal-Fenster
date +%d.%m.%Y
whoami
getent passwd "$USER" | cut -d: -f5 | cut -d, -f1

Der dritte Befehl holt den vollständigen Namen aus /etc/passwd (Feld 5, bis zum ersten Komma), falls er bei der Installation angegeben wurde. Um die Ausgabe eines Befehls in einer Variablen zu speichern, dient die Befehlssubstitution $(befehl): Die Shell führt den Befehl aus und setzt seine Ausgabe an dieser Stelle ein.

addscript.sh
# Variablen
dateiname="$1"
benutzung="Benutzung: $0 skriptname.sh"
datum=$(date +%d.%m.%Y)
verfasser=$(whoami)
addscript.sh
# Zweck des Skripts
#
# Datum / Name
# $datum / $verfasser
#

Testen. Wie nach jeder Änderung. Stimmt der Kopf des erzeugten Skripts?

Noch prüft addscript.sh nicht, ob die Zieldatei schon existiert. Fatal, wenn ein mit viel Hirnschmalz geschriebenes Skript durch den Generator einfach überschrieben würde. Der Test dafür steht in der Tabelle oben: -e.

Füge den Block direkt nach der Argumentprüfung ein:

addscript.sh
# Aufruf prüfen, im Fehlerfall Hinweis ausgeben und abbrechen
if [[ $# -lt 1 ]]; then
echo "$benutzung"
exit 1
fi
# Abbruch, falls die Datei bereits existiert
if [[ -e "$dateiname" ]]; then
echo "$0: Fehler, $dateiname existiert bereits."
exit 1
fi

Testen. Nimm als Argument zuerst eine Datei, um die es nicht schade wäre. Sicher ist sicher.

Terminal-Fenster
./addscript.sh neu.sh
echo $?

Zur Kontrolle das vollständige Skript:

addscript.sh
#!/bin/bash
#
# addscript.sh
# Erstellt ein Skript aus dem Kurs-Template, macht es
# ausführbar und öffnet es im Editor.
#
# 2026-11-05 / Vorname Nachname
#
# Variablen
dateiname="$1"
benutzung="Benutzung: $0 skriptname.sh"
datum=$(date +%d.%m.%Y)
verfasser=$(whoami)
# Hauptprogramm
# Aufruf prüfen, im Fehlerfall Hinweis ausgeben und abbrechen
if [[ $# -lt 1 ]]; then
echo "$benutzung"
exit 1
fi
# Abbruch, falls die Datei bereits existiert
if [[ -e "$dateiname" ]]; then
echo "$0: Fehler, $dateiname existiert bereits."
exit 1
fi
# Template mit echo ausgeben und in die Datei umleiten
echo "#!/bin/bash
#
# $dateiname
# Zweck des Skripts
#
# $datum / $verfasser
#
# Variablen
# Funktionen
# Hauptprogramm" > "$dateiname"
# Ausführbar machen und im Editor öffnen
chmod +x "$dateiname"
vim "$dateiname"

Das Flowchart weiter oben stimmt damit nicht mehr. Das ist Teil deiner Aufgabe.

Lösung anzeigen

Das Skript mit allen Schritten. Die Variable programm ersetzt $0 in den Meldungen, damit nicht ./addscript.sh dort steht, sondern nur der Name:

addscript.sh
#!/bin/bash
#
# addscript.sh
# Erstellt ein Skript aus dem Kurs-Template, macht es
# ausführbar und öffnet es im Editor.
#
# Benutzung: addscript.sh skriptname.sh ["Zweck des Skripts"]
#
# 2026-11-05 / Vorname Nachname
#
# Variablen
dateiname="$1"
programm=$(basename "$0")
benutzung="Benutzung: $programm skriptname.sh [\"Zweck des Skripts\"]"
datum=$(date +%d.%m.%Y)
# Vollständiger Name aus /etc/passwd (Feld 5, bis zum ersten Komma).
# Falls das Feld leer ist, stattdessen: verfasser=$(whoami)
verfasser=$(getent passwd "$USER" | cut -d: -f5 | cut -d, -f1)
# Hauptprogramm
# Aufruf prüfen: mindestens ein Argument
if [[ $# -lt 1 ]]; then
echo "$benutzung"
exit 1
fi
# Abbruch, falls die Datei bereits existiert
if [[ -e "$dateiname" ]]; then
echo "$programm: Fehler, $dateiname existiert bereits."
exit 1
fi
# Zweck als zweites Argument, sonst nachfragen
if [[ -n "$2" ]]; then
zweck="$2"
else
read -r -p "Zweck des Skripts: " zweck
fi
# Template mit echo ausgeben und in die Datei umleiten
echo "#!/bin/bash
#
# $dateiname
# $zweck
#
# $datum / $verfasser
#
# Variablen
# Funktionen
# Hauptprogramm" > "$dateiname"
# Ausführbar machen und im Editor öffnen
chmod +x "$dateiname"
vim "$dateiname"

Das Flowchart dazu:

flowchart TD
    S(["Start"]) --> P{"Argument vorhanden?"}
    P -->|nein| M["Benutzung ausgeben"] --> E1(["Ende, exit 1"])
    P -->|ja| EX{"Datei existiert schon?"}
    EX -->|ja| F["Fehlermeldung ausgeben"] --> E2(["Ende, exit 1"])
    EX -->|nein| Z{"Zweck als 2. Argument?"}
    Z -->|nein| R["Zweck mit read abfragen"] --> W
    Z -->|ja| W["Template in Datei schreiben"]
    W --> X["Datei ausführbar machen"]
    X --> ED["Editor öffnen"]
    ED --> E0(["Ende, exit 0"])

Und der Quelltext für addscript-flowchart.md:

addscript-flowchart.md
# Flowchart addscript.sh
```mermaid
flowchart TD
S(["Start"]) --> P{"Argument vorhanden?"}
P -->|nein| M["Benutzung ausgeben"] --> E1(["Ende, exit 1"])
P -->|ja| EX{"Datei existiert schon?"}
EX -->|ja| F["Fehlermeldung ausgeben"] --> E2(["Ende, exit 1"])
EX -->|nein| Z{"Zweck als 2. Argument?"}
Z -->|nein| R["Zweck mit read abfragen"] --> W
Z -->|ja| W["Template in Datei schreiben"]
W --> X["Datei ausführbar machen"]
X --> ED["Editor öffnen"]
ED --> E0(["Ende, exit 0"])
```

Selbstcheck · 6 Fragen

1. Wozu dient die erste Zeile #!/bin/bash?
2. Warum muss ein Skript im aktuellen Verzeichnis mit ./hallo.sh aufgerufen werden?
3. Was enthält $# in einem Skript?
4. Was bedeutet exit 1 am Ende eines Skripts?
5. Welcher Test prüft, ob eine Datei existiert?
6. Welche Aussagen zu Variablen stimmen? (mehrere Antworten möglich)