Übungsaufgaben zu Skripten zur Systemautomatisierung

https://bildung.social/@oerinformatik/

https://oer-informatik.de/skripte-uebungsaufgaben

Skripte, Systemautomatisierung, PowerShell, Bash, Python, JSON, cron, Struktogramm, Übungsaufgaben, Prüfungsvorbereitung

tl/dr; (ca. 88 min Lese- und Bearbeitungszeit): Eine umfangreiche Übungsaufgabe: Für einen Fahrrad-Onlineshop automatisierst Du Routinearbeiten. Du schreibst ein Skript für einen Akkustand-Bericht mit JSON-Ausgabe, ergänzt ein Struktogramm für einen Netzscan, bildest Benutzernamen aus einer CSV-Datei, kommentierst ein Sicherungsskript, korrigierst ein fehlerhaftes Prüfskript mit englischer Befehlsreferenz, setzt Befehle mit Parametern zusammen, überwachst einen Webshop und beurteilst die Sicherheit von Skripten. Alle Lösungen sind ausgeblendet und mit Punkteverteilung versehen [AI-Assisted]. (Zuletzt geändert am 28.10.2026)

Diese Übungsaufgabe gehört zum Artikel Skripte zur Systemautomatisierung.

Hinweise zur Bearbeitung: Als FIA musst Du zur Teil-2-Prüfung Aufgaben auf Kernkompetenz-Ebene lösen können, das ist Aufgabe 2.1. Wenn nichts anderes verlangt ist, darfst Du in Pseudocode oder in einer Skriptsprache Deiner Wahl (PowerShell, Bash, Python) antworten. Bewertet wird die Logik, nicht jedes Satzzeichen – kommentiere Deine Schritte, damit die Logik erkennbar ist. Schreib Deine Lösung zuerst vollständig auf, bevor Du die Beispiellösung aufklappst.

Die Codebeispiele kannst Du in vier Notationen anzeigen. Die Notationen sind im Artikel Algorithmen mit Pseudocode dokumentieren beschrieben.

laden: integer := 0

Aufgabe 2.1: Routinearbeiten in der IT eines Fahrrad-Onlineshops automatisieren (Kernkompetenz)

Die CSB-Rad GmbH in Bielefeld verkauft Fahrräder und Komponenten über einen Onlineshop und lässt Rahmen nach Maß fertigen. Du bist Auszubildende*r der Fachinformatik und arbeitest zusammen mit Deinem Mit-Azubi Jonas für den Geschäftsführer Technik und IT, Cem Sarıkaya.

Nach einem Sicherheits-Check wurde die IT im Werk Sennestadt umgebaut: Die Daten liegen jetzt auf einem neuen NAS (Linux-basiert, Hostname nas-sen), die Handscanner (MDE-Geräte) im Lager und die Tablets der Werkstatt hängen in einem eigenen Netz 192.168.20.0/24, und statt des gemeinsam genutzten Kontos admin bekommt jede Person ein eigenes Konto im Verzeichnisdienst. Cem Sarıkaya möchte, dass wiederkehrende Arbeiten künftig von Skripten erledigt werden: „Was wir jeden Morgen von Hand prüfen, vergessen wir irgendwann.“

a) Jonas würde die Werkzeuge lieber als C#-Programm schreiben: „Skriptsprachen sind doch nur Spielerei.“

aa) Erläutere je einen Vorteil einer Skriptsprache und einer Compilersprache für die Automatisierung administrativer Aufgaben. (4 Punkte)

Je 2 Punkte für einen erläuterten Vorteil (Vorteil nennen 1 Punkt, Begründung 1 Punkt), zum Beispiel:

Skriptsprache (PowerShell, Bash, Python):

  • Änderungen sind sofort wirksam, weil der Quelltext zur Laufzeit von einem Interpreter ausgeführt wird; es muss nicht neu kompiliert und verteilt werden. Das passt zu Verwaltungsaufgaben, die sich häufig ändern (neuer Pfad, neue Schwelle).

  • Gute Eignung für systemnahe Aufgaben: Befehle des Betriebssystems (Konten anlegen, Dateien kopieren, Dienste prüfen) lassen sich direkt aufrufen und verketten.

  • Der Quelltext ist auf dem Zielsystem lesbar und damit leicht prüfbar und dokumentiert.

Compilersprache (C#, Java, C++):

  • Übersetzte Programme laufen meist schneller – wichtig bei rechenintensiven Aufgaben oder sehr großen Datenmengen.

  • Der Compiler findet Syntax- und Typfehler vor dem ersten Lauf, statt dass das Skript erst mitten in der Ausführung abbricht.

  • Leistungsfähige Entwicklungsumgebungen (Debugger, Refactoring, Tests) unterstützen größere, langlebige Programme.

ab) Nenne zwei weitere Routineaufgaben aus der Systembetreuung, die sich gut mit einem Skript automatisieren lassen. (2 Punkte)

Je 1 Punkt, zum Beispiel:

  • Benutzerkonten aus einer Liste anlegen, sperren oder löschen

  • Datensicherung erstellen, prüfen und alte Sicherungen löschen

  • Speicherplatz, Erreichbarkeit von Geräten und Diensten oder Ablaufdaten von Kennwörtern und Zertifikaten überwachen und bei Grenzwerten warnen

  • Geräte im Netz inventarisieren

  • Protokolldateien auswerten oder archivieren, temporäre Dateien aufräumen

  • Software-Updates oder Konfigurationen auf viele Rechner verteilen

ac) Welche der folgenden Aussagen über Skriptsprachen trifft zu? Gib die Ziffer an und begründe kurz. (2 Punkte)

  1. Skriptsprachen können nur Befehle der Kommandozeile aneinanderreihen, Schleifen und Verzweigungen gibt es nicht.

  2. Skripte werden meist von einem Interpreter ausgeführt, der auf dem Zielsystem vorhanden sein muss.

  3. Skriptsprachen werden ausschließlich in der Webprogrammierung eingesetzt.

  4. Skripte laufen nur auf dem Prozessor, für den sie geschrieben wurden.

Aussage 2 trifft zu (1 Punkt): Der Quelltext wird zur Laufzeit von einem Interpreter (z. B. bash, powershell.exe/pwsh, python3) gelesen und ausgeführt; fehlt der Interpreter auf dem Zielsystem, läuft das Skript nicht (1 Punkt Begründung).

Die anderen Aussagen sind falsch: Skriptsprachen haben Variablen, Schleifen, Verzweigungen und Funktionen (1), werden vor allem auch in der Systemverwaltung, Datenanalyse und Automatisierung eingesetzt (3) und sind gerade nicht an einen Prozessor gebunden, weil der Interpreter die Übersetzung übernimmt (4).

b) Im Lager und in der Werkstatt sind fünf MDE-Geräte und vier Tablets im Einsatz. Ihre Inventarnummern und IP-Adressen stehen in der Datei C:\Skripte\geraete.csv:

Inventarnr;Typ;IP
MDE-01;MDE;192.168.20.101
MDE-02;MDE;192.168.20.102
MDE-03;MDE;192.168.20.103
MDE-04;MDE;192.168.20.104
MDE-05;MDE;192.168.20.105
TAB-W1;Tablet;192.168.20.111
TAB-W2;Tablet;192.168.20.112
TAB-W3;Tablet;192.168.20.113
TAB-W4;Tablet;192.168.20.114

Die Geräteverwaltung des Lagersoftware-Anbieters stellt die Funktion Get-Akkustand(IP) bereit. Sie liefert den Ladezustand des Geräts mit dieser IP-Adresse in Prozent (0 bis 100) oder null (PowerShell: $null), wenn das Gerät nicht antwortet.

Jeden Morgen vor Schichtbeginn soll ein Skript einen Bericht erstellen:

  • Für jedes Gerät aus der CSV-Datei wird der Akkustand abgefragt.

  • Der Status ist offline, wenn kein Akkustand geliefert wird, laden, wenn der Akkustand unter 30 % liegt, sonst ok.

  • Inventarnummer, IP-Adresse, Akkustand und Status aller Geräte werden als JSON-Array in die Datei C:\Berichte\akkustand.json geschrieben.

  • Zum Schluss gibt das Skript aus, wie viele Geräte geladen werden müssen und wie viele offline sind.

ba) Schreibe dieses Skript in Pseudocode oder in einer Skriptsprache Deiner Wahl. (12 Punkte)

Punkteverteilung:

Bewertung Teilaufgabe ba)
Teil Punkte
CSV-Datei mit Trennzeichen ; einlesen 2
Schleife über alle Geräte 2
Get-Akkustand mit der IP-Adresse des Geräts aufrufen 1
Verzweigung offline (Prüfung auf null zuerst) / laden (< 30) / ok 3
Eintrag mit Inventarnummer, IP, Akkustand und Status an die Ergebnisliste anhängen 2
Ergebnisliste als JSON in die Datei schreiben 1
Zähler für laden und offline und Ausgabe am Ende 1

Lösung in PowerShell:

# Akkustand aller MDE-Geraete und Tablets als JSON-Bericht speichern
$geraete = Import-Csv -Path "C:\Skripte\geraete.csv" -Delimiter ";"
$bericht = @()                            # leeres Array fuer die Ergebnisse
$laden   = 0
$offline = 0

foreach ($g in $geraete) {
    $akku = Get-Akkustand -IP $g.IP       # 0 bis 100 oder $null
    if ($null -eq $akku) {
        $status = "offline"
        $offline++
    } elseif ($akku -lt 30) {
        $status = "laden"
        $laden++
    } else {
        $status = "ok"
    }
    $bericht += [PSCustomObject]@{
        inventarnr = $g.Inventarnr
        ip         = $g.IP
        akku       = $akku
        status     = $status
    }
}

ConvertTo-Json -InputObject $bericht |
    Out-File -FilePath "C:\Berichte\akkustand.json" -Encoding utf8
Write-Host "$laden Geraete laden, $offline Geraete offline"

Lösung in Python (die Funktion heißt dort get_akkustand und liefert None statt null):

import csv
import json
from teutomdm import get_akkustand      # liefert 0 bis 100 oder None

bericht = []
laden = 0
offline = 0

with open(r"C:\Skripte\geraete.csv", encoding="utf-8", newline="") as datei:
    for g in csv.DictReader(datei, delimiter=";"):
        akku = get_akkustand(g["IP"])
        if akku is None:
            status = "offline"
            offline += 1
        elif akku < 30:
            status = "laden"
            laden += 1
        else:
            status = "ok"
        bericht.append({"inventarnr": g["Inventarnr"], "ip": g["IP"],
                        "akku": akku, "status": status})

with open(r"C:\Berichte\akkustand.json", "w", encoding="utf-8") as datei:
    json.dump(bericht, datei, indent=2)
print(f"{laden} Geraete laden, {offline} Geraete offline")

Lösung in Pseudocode:

Prozedur akkuBericht()
    geraete := leseCSV("C:\Skripte\geraete.csv", ";")
    bericht := leere Liste
    laden: integer := 0
    offline: integer := 0
    für jedes g in geraete
        akku := Get-Akkustand(g.IP)
        wenn akku = null dann
            status := "offline"
            offline := offline + 1
        sonst wenn akku < 30 dann
            status := "laden"
            laden := laden + 1
        sonst
            status := "ok"
        Ende wenn
        bericht.anhaengen({inventarnr: g.Inventarnr, ip: g.IP, akku: akku, status: status})
    Ende für
    schreibeJSON("C:\Berichte\akkustand.json", bericht)
    Ausgabe laden & " Geräte laden, " & offline & " Geräte offline"
Ende akkuBericht

Bewertungshinweise:

  • Die Prüfung auf null muss vor dem Vergleich < 30 stehen. In vielen Sprachen gilt null < 30 sonst als wahr (PowerShell) oder führt zu einem Fehler (Python) – ein ausgeschaltetes Gerät würde dann als „laden“ gemeldet oder das Skript bricht ab.

  • In PowerShell gehört $null auf die linke Seite des Vergleichs ($null -eq $akku).

  • Ein Gerät mit genau 30 % hat den Status ok („unter 30 %“).

  • Mit Beispielwerten (MDE-02: 27 %, MDE-03: offline, MDE-04: 30 %, MDE-05: 12 %, TAB-W2: offline, TAB-W3: 29 %, übrige Geräte über 30 %) gibt das Skript 3 Geraete laden, 2 Geraete offline aus. Ein Eintrag der JSON-Datei sieht so aus:

{
  "inventarnr": "MDE-03",
  "ip": "192.168.20.103",
  "akku": null,
  "status": "offline"
}

bb) Cem Sarıkaya möchte außerdem erkennen, ob im Netz 192.168.20.0/24 Geräte hängen, die in keiner Liste stehen. Die Funktion erreichbar(ip) liefert true, wenn unter der Adresse ein Gerät antwortet; enthaelt(liste, wert) liefert true, wenn der Wert in der Liste vorkommt. Die Liste bekannt enthält die IP-Adressen aller bekannten Geräte. Die Funktion soll mit unbekannteGeraete("192.168.20", bekannt) aufgerufen werden.

Vervollständige das Struktogramm, indem Du die Lücken (1) bis (5) ausfüllst. (5 Punkte)

Struktogramm der Funktion unbekannteGeraete mit fünf Lücken: Grenzen der Zählschleife, Zusammensetzen der IP-Adresse, zweite Bedingung und Anweisung im Ja-Zweig (mit KI erstellt)

Je 1 Punkt:

Lösung der Lücken im Struktogramm
Lücke Inhalt
(1) 1 (die Adresse .0 ist die Netzadresse)
(2) 254 (die Adresse .255 ist die Broadcastadresse)
(3) praefix & "." & host
(4) nicht enthaelt(bekannt, ip)
(5) unbekannt.anhaengen(ip)
Vollständiges Struktogramm der Funktion unbekannteGeraete (mit KI erstellt)

bc) Die Liste bekannt enthält die neun Geräte aus der CSV-Datei und das Gateway 192.168.20.1. Bei einem Testlauf antworten die Adressen .1, .101 bis .105, .111, .113, .114 und .150. Gib den Rückgabewert der Funktion an. (2 Punkte)

Rückgabe: eine Liste mit genau einem Eintrag, ["192.168.20.150"] (2 Punkte).

Alle anderen antwortenden Adressen stehen in bekannt. Dass .112 (TAB-W2) nicht antwortet, ist für diese Funktion unerheblich – sie sucht nur Geräte, die antworten, aber nicht bekannt sind.

bd) Jonas ruft die Funktion im Hauptskript auf und möchte danach mit ausgabe(unbekannt) die Liste anzeigen. Das Skript meldet, dass unbekannt nicht definiert ist. Erläutere die Ursache und wie Jonas an das Ergebnis kommt. (3 Punkte)

  • unbekannt ist eine lokale Variable der Funktion: Sie existiert nur innerhalb der Funktion, solange diese läuft, und ist von außen nicht sichtbar (1 Punkt).

  • Das ist gewollt: Lokale Variablen verhindern, dass Funktionen versehentlich Werte im Hauptskript oder in anderen Funktionen überschreiben, und machen Funktionen wiederverwendbar. Globale Variablen wären überall les- und änderbar, damit aber auch überall versehentlich veränderbar (1 Punkt).

  • Lösung: den Rückgabewert der Funktion einer Variablen zuweisen, z. B. fremde := unbekannteGeraete("192.168.20", bekannt) und dann ausgabe(fremde) (1 Punkt).

be) Jonas schlägt vor, den Akkustand-Bericht als CSV-Datei statt als JSON zu speichern, „weil man die in der Tabellenkalkulation öffnen kann“. Entscheide Dich für ein Format und begründe Deine Entscheidung mit je einem Pro- und einem Kontra-Argument. (4 Punkte)

Entscheidung mit Pro- und Kontra-Argument, je 2 Punkte. Beide Entscheidungen sind möglich, zum Beispiel:

JSON

  • Pro: Datentypen bleiben erhalten (Zahl, Text, null für „offline“), verschachtelte Daten sind möglich, und Webanwendungen, Dashboards oder andere Skripte können JSON ohne eigene Umwandlung einlesen.

  • Kontra: Für Menschen weniger übersichtlich; in der Tabellenkalkulation nur über einen Import zu öffnen; mehr Speicherbedarf durch die wiederholten Schlüsselnamen.

CSV

  • Pro: einfach aufgebaut, platzsparend, direkt in jeder Tabellenkalkulation zu öffnen und zu sortieren.

  • Kontra: nicht einheitlich genormt (Trennzeichen, Anführungszeichen, Zeichenkodierung, Dezimalkomma), keine Datentypen (alles ist Text, ein fehlender Akkustand ist nur eine leere Zelle), keine Verschachtelung.

c) Für die Handscanner bekommen fünf Lagerbeschäftigte eigene Konten. Ein Skript soll sie aus der Datei lager_neu.csv anlegen:

Vorname;Nachname;Abteilung
Uwe;Brinkmann;Lager
Svetlana;Krüger;Lager
Malte;Groß;Lager
Aylin;Kaya;Lager
Jörn;Möller;Lager

Der Benutzername wird nach folgender Regel gebildet: erster Buchstabe des Vornamens und der Nachname, alles in Kleinbuchstaben; ä, ö, ü werden durch ae, oe, ue ersetzt und ß durch ss; Leerzeichen und Bindestriche werden entfernt.

ca) Schreibe eine Funktion benutzername(vorname, nachname), die den Benutzernamen nach dieser Regel zurückgibt. Du darfst übliche Zeichenkettenfunktionen wie teilzeichenkette, kleinbuchstaben und ersetze verwenden. (6 Punkte)

Punkteverteilung: Funktionskopf mit zwei Parametern und Rückgabe 1 Punkt, erster Buchstabe des Vornamens 1 Punkt, Verkettung mit dem Nachnamen 1 Punkt, Kleinschreibung 1 Punkt, Ersetzen der vier Sonderzeichen 1 Punkt, Entfernen von Leerzeichen und Bindestrichen 1 Punkt.

Pseudocode:

Funktion benutzername(vorname: string, nachname: string): string
    name: string := kleinbuchstaben(teilzeichenkette(vorname, 0, 1) & nachname)
    name := ersetze(name, "ä", "ae")
    name := ersetze(name, "ö", "oe")
    name := ersetze(name, "ü", "ue")
    name := ersetze(name, "ß", "ss")
    name := ersetze(name, " ", "")
    name := ersetze(name, "-", "")
    Rückgabe name
Ende benutzername

PowerShell:

function Get-Benutzername {
    param([string]$Vorname, [string]$Nachname)
    $name = ($Vorname.Substring(0, 1) + $Nachname).ToLower()   # Initiale + Nachname, klein
    $name = $name -replace 'ä', 'ae' -replace 'ö', 'oe' -replace 'ü', 'ue' -replace 'ß', 'ss'
    $name = $name -replace '[ -]', ''                          # Leerzeichen und Bindestriche entfernen
    return $name
}

Python:

def benutzername(vorname: str, nachname: str) -> str:
    name = (vorname[0] + nachname).lower()            # Initiale + Nachname, klein
    for alt, neu in (("ä", "ae"), ("ö", "oe"), ("ü", "ue"), ("ß", "ss")):
        name = name.replace(alt, neu)
    return name.replace(" ", "").replace("-", "")     # Leerzeichen und Bindestriche entfernen

Erst in Kleinbuchstaben umwandeln, dann ersetzen: So reicht je eine Ersetzung für ä und Ä (ein Vorname wie „Ümit“ ergibt ue…).

cb) Gib die Benutzernamen für Svetlana Krüger, Malte Groß und Jörn Möller an. (3 Punkte)

Je 1 Punkt: skrueger, mgross, jmoeller.

cc) Erläutere, warum Benutzernamen und E-Mail-Adressen ohne Umlaute gebildet werden. (2 Punkte)

Je 1 Punkt für zwei Gründe, zum Beispiel:

  • Nicht alle Systeme und Protokolle verarbeiten Umlaute zuverlässig: Ältere Anwendungen, Datenbanken oder Mailserver nutzen andere Zeichenkodierungen; E-Mails an Adressen mit Umlauten (internationalisierte Adressen) werden nicht überall zugestellt.

  • Personen mit einem Tastaturlayout ohne Umlaute (z. B. Gäste, ausländische Partner, Bildschirmtastaturen der MDE-Geräte) können solche Namen nur umständlich eingeben.

  • Uneinheitliche Schreibweisen (Groß, Gross, GROSS) führen zu doppelten oder nicht gefundenen Konten.

cd) Im nächsten Jahr soll Maike Groß eingestellt werden. Beschreibe, welches Problem das Skript dann hat, und wie es gelöst werden kann. (2 Punkte)

  • Problem (1 Punkt): Für Maike Groß ergibt sich ebenfalls mgross. Benutzernamen müssen eindeutig sein; das Anlegen schlägt fehl oder – schlimmer – das Skript ändert das vorhandene Konto von Malte Groß.

  • Lösung (1 Punkt): Vor dem Anlegen prüfen, ob der Name schon existiert, und dann z. B. eine fortlaufende Zahl anhängen (mgross2) oder einen weiteren Buchstaben des Vornamens verwenden (magross); zumindest den Fall protokollieren und zur manuellen Bearbeitung melden.

d) Auf dem NAS sichert das folgende Bash-Skript jede Nacht die Daten auf eine USB-Festplatte. Es wurde vom Lieferanten des NAS eingerichtet.

#!/bin/bash
# Schritt 1
QUELLE="/volume1/daten"
ZIEL="/mnt/usb-sicherung"
DATUM=$(date +%F)
LOG="/var/log/sicherung.log"

# Schritt 2
if ! mountpoint -q "$ZIEL"; then
    echo "$DATUM FEHLER: Sicherungsplatte nicht eingehaengt" >> "$LOG"
    exit 1
fi

# Schritt 3
tar -czf "$ZIEL/daten_$DATUM.tar.gz" "$QUELLE"

# Schritt 4
sha256sum "$ZIEL/daten_$DATUM.tar.gz" > "$ZIEL/daten_$DATUM.sha256"

# Schritt 5
find "$ZIEL" -name "daten_*" -mtime +13 -delete

# Schritt 6
echo "$DATUM OK: daten_$DATUM.tar.gz erstellt" >> "$LOG"

Auszug aus der Befehlsreferenz:

date +%F prints the current date in the format YYYY-MM-DD. mountpoint -q DIR returns 0 if DIR is a mount point, otherwise a non-zero value; -q suppresses all output. tar -czf ARCHIVE FILES creates (c) a gzip-compressed (z) archive file (f). sha256sum FILE prints the SHA-256 checksum of FILE. find DIR -name PATTERN -mtime +N -delete deletes files below DIR whose names match PATTERN and whose data was last modified more than N days ago (fractional days are ignored, so +13 means at least 14 days).

da) Kommentiere für die Dokumentation die Schritte 1 bis 6. (6 Punkte)

Je 1 Punkt:

Kommentare zum Sicherungsskript
Schritt Kommentar
1 Variablen festlegen: Quellordner der Daten, Zielordner auf der USB-Platte, aktuelles Datum im Format JJJJ-MM-TT (für Dateinamen) und Pfad der Protokolldatei.
2 Prüfen, ob die USB-Platte unter dem Zielordner eingehängt ist. Falls nicht: Fehler mit Datum ins Protokoll schreiben und das Skript mit Exit-Code 1 (Fehler) beenden – sonst würde die Sicherung auf die interne Platte geschrieben.
3 Den Quellordner in ein gzip-komprimiertes Archiv daten_<Datum>.tar.gz auf der USB-Platte packen (Vollsicherung).
4 Die SHA-256-Prüfsumme des Archivs berechnen und in einer eigenen Datei speichern, um später prüfen zu können, ob das Archiv unverändert und unbeschädigt ist.
5 Alte Sicherungen (Archive und Prüfsummendateien), deren Änderung mindestens 14 Tage zurückliegt, löschen, damit die Platte nicht vollläuft (Rotation).
6 Erfolgreiche Sicherung mit Datum und Dateinamen im Protokoll vermerken (Anhängen mit >>).

db) Gib an, wie viele Tagessicherungen das Skript bei täglicher Ausführung ungefähr vorhält, und begründe Deine Angabe. (2 Punkte)

Etwa 14 Sicherungen (1 Punkt): Schritt 5 löscht nur Dateien, die mindestens 14 volle Tage alt sind. Nach dem Lauf bleiben die heutige Sicherung und die der 13 Vortage übrig (1 Punkt). Weil die Archive nicht jede Nacht auf die Minute gleich fertig werden, kann an der Grenze auch einmal eine 15. Sicherung übrig bleiben; diese Angabe ist ebenfalls richtig.

dc) Cem Sarıkaya kopiert das Skript in einen neuen Ordner und startet es mit ./sicherung.sh. Die Shell antwortet mit Permission denied. ls -l zeigt -rw-r--r-- 1 root root 512 sicherung.sh. Gib den Befehl an, der nur dem Besitzer das Ausführungsrecht gibt, und erläutere die Aufgabe der ersten Zeile #!/bin/bash. (3 Punkte)

  • chmod u+x sicherung.sh (gleichwertig: chmod 744 sicherung.sh) – 2 Punkte. Danach zeigt ls -l -rwxr--r--.

  • Die erste Zeile (Shebang) legt fest, welcher Interpreter das Skript ausführt, wenn es direkt mit ./sicherung.sh gestartet wird – hier die Bash (1 Punkt).

dd) Das Skript soll von cron montags bis samstags um 23:30 Uhr gestartet werden. Es liegt unter /volume1/skripte/sicherung.sh. Gib die Zeile für die crontab an. (3 Punkte)

crontab(5), excerpt: Each line has five time and date fields followed by the command: minute (0–59), hour (0–23), day of month (1–31), month (1–12), day of week (0–7, where both 0 and 7 mean Sunday). A field may be an asterisk (*), which stands for “every value”. Ranges of numbers are allowed, e.g. 8-11, and lists, e.g. 1,3,5.

30 23 * * 1-6 /volume1/skripte/sicherung.sh

1 Punkt für Minute und Stunde (30 23), 1 Punkt für die Wochentage 1-6 (gleichwertig 1,2,3,4,5,6) mit * für Tag und Monat, 1 Punkt für den vollständigen Pfad des Skripts. Auf einem Windows-Server übernimmt die Aufgabenplanung diese Rolle.

e) Natalia Wróbel aus der Buchhaltung soll jeden Morgen sehen, ob die Sicherung gelaufen ist. Jonas hat dazu das folgende PowerShell-Skript geschrieben:

1  $Ordner  = "\\nas-sen\sicherung"
2  $Neueste = Get-ChildItem -Path $Ordner -Filter "daten_*.tar.gz" |
3             Sort-Object -Property LastWriteTime |
4             Select-Object -First 1
5  $Alter   = (Get-Date) - $Neueste.LastWriteTime
6  if ($Alter.TotalHours > 26) {
7      Write-Warning "Letzte Sicherung ist $([int]$Alter.TotalHours) Stunden alt!"
8  } else {
9      Write-Host "Sicherung aktuell: $($Neueste.Name)"
10 }

Obwohl die USB-Platte seit zwei Tagen fehlt, meldet das Skript jeden Morgen „Sicherung aktuell“ – und zwar mit dem Namen einer zwei Wochen alten Sicherung. Außerdem liegt im Ordner, aus dem das Skript gestartet wird, plötzlich eine Datei namens 26.

Auszug aus der Befehlsreferenz:

Sort-Object -Property sorts objects by the values of the given property. -Descending indicates that the objects are sorted in descending order; the default is ascending order. Select-Object -First N selects the first N objects. Comparison operators: -eq equal, -ne not equal, -gt greater than, -ge greater than or equal, -lt less than, -le less than or equal. The characters > and >> are redirection operators: they send the output of a command to a file.

Korrigiere die zwei Fehler. Gib jeweils die Zeile, die korrigierte Anweisung und eine kurze Begründung an. (6 Punkte)

Je Fehler 1 Punkt für die Zeile, 1 Punkt für die Korrektur, 1 Punkt für die Begründung:

Korrekturen im Prüfskript
Zeile Korrektur Begründung
3 Sort-Object -Property LastWriteTime -Descending (oder in Zeile 4 Select-Object -Last 1) Sort-Object sortiert standardmäßig aufsteigend; -First 1 liefert dann die älteste Sicherung statt der neuesten.
6 if ($Alter.TotalHours -gt 26) { > ist in PowerShell kein Vergleich, sondern eine Umleitung: Der Wert wird in die Datei 26 geschrieben, der Ausdruck liefert nichts, die Bedingung ist nie erfüllt – es wird immer der else-Zweig ausgeführt.

Zusätzlicher Hinweis (ohne Punkte): Liegt gar keine Sicherung im Ordner, ist $Neueste leer. Ein robustes Skript prüft das vorher (if ($null -eq $Neueste) { … }) und warnt ebenfalls.

f) Die Werkstatt fotografiert jedes Fahrrad bei Annahme und Übergabe. Der Ordner \\nas-sen\werkstatt\fotos wächst schnell und hat viele Unterordner (je Jahr und Auftrag). Fotos haben die Endungen .jpg und .heic; manche Tablets legen sie als versteckte Dateien ab.

Beispiele aus der Hilfe zu Get-ChildItem:

Get-ChildItem -Path D:\Daten -File

listet nur Dateien (keine Ordner) in D:\Daten auf.

Get-ChildItem -Path D:\Daten -Recurse

listet den Inhalt von D:\Daten und allen Unterordnern auf.

Get-ChildItem -Path D:\Daten -Recurse -Include *.txt,*.log

listet nur Elemente mit den Endungen .txt oder .log auf (-Include wirkt nur zusammen mit -Recurse oder einem Platzhalter im Pfad).

Get-ChildItem -Path D:\Daten -Force

listet zusätzlich versteckte Elemente und Systemdateien auf.

fa) Gib den Aufruf von Get-ChildItem an, der alle Fotos (.jpg und .heic, auch versteckte) im Ordner \\nas-sen\werkstatt\fotos einschließlich aller Unterordner liefert – und nur Dateien. (4 Punkte)

Get-ChildItem -Path \\nas-sen\werkstatt\fotos -Recurse -File -Force -Include *.jpg,*.heic

Je 1 Punkt für -Recurse, -File, -Force und -Include *.jpg,*.heic (mit -Path). Die Reihenfolge der Parameter ist beliebig. Ohne -Force fehlen die versteckten Dateien; ohne -Recurse liefert -Include hier gar nichts, weil der Pfad keinen Platzhalter enthält.

fb) Ins Archiv \\nas-sen\archiv\fotos verschoben werden sollen alle Fotos, die seit mehr als 365 Tagen nicht geändert wurden, und außerdem alle Fotos größer als 20 MB, die seit mehr als 90 Tagen nicht geändert wurden. Ergänze den Aufruf aus fa) um einen Filter und das Verschieben. Die Eigenschaften LastWriteTime (Zeitpunkt der letzten Änderung) und Length (Größe in Byte) stehen zur Verfügung; (Get-Date).AddDays(-n) liefert den Zeitpunkt vor n Tagen, 20MB steht in PowerShell für 20 MiB. (5 Punkte)

$vor365 = (Get-Date).AddDays(-365)
$vor90  = (Get-Date).AddDays(-90)
Get-ChildItem -Path \\nas-sen\werkstatt\fotos -Recurse -File -Force -Include *.jpg,*.heic |
    Where-Object { $_.LastWriteTime -lt $vor365 -or
                   ($_.LastWriteTime -lt $vor90 -and $_.Length -gt 20MB) } |
    Move-Item -Destination \\nas-sen\archiv\fotos

Punkteverteilung: Filter mit Where-Object und $_ 1 Punkt, Bedingung „älter als 365 Tage“ (-lt, also ein früherer Zeitpunkt) 1 Punkt, Bedingung „älter als 90 Tage und größer als 20 MB“ 1 Punkt, richtige Verknüpfung mit -or und Klammern 1 Punkt, Move-Item -Destination … 1 Punkt.

Hinweise: Mit dem Parameter -WhatIf an Move-Item zeigt PowerShell nur an, was verschoben würde – so testest Du das Skript gefahrlos. Move-Item legt alle Dateien direkt in den Zielordner; soll die Ordnerstruktur erhalten bleiben oder kommen gleiche Dateinamen in verschiedenen Unterordnern vor, muss das Skript den Zielpfad je Datei bilden (oder z. B. robocopy mit /MOV und /MINAGE verwenden).

g) Am Black Friday war der Onlineshop eine Stunde lang nicht nutzbar, ohne dass es jemand bemerkte. Jonas testet deshalb vom Büro aus:

PS C:\> Test-NetConnection -ComputerName shop.csb-rad.example -Port 443      (1)

ComputerName     : shop.csb-rad.example
RemoteAddress    : 203.0.113.80                                              (2)
RemotePort       : 443
InterfaceAlias   : Ethernet
SourceAddress    : 192.168.10.23                                             (3)
TcpTestSucceeded : True                                                      (4)

Zur selben Zeit zeigt der Browser beim Aufruf des Shops die Fehlermeldung „503 Service Unavailable“.

ga) Beschreibe stichwortartig die Bedeutung der mit (1) bis (4) markierten Ein- und Ausgaben. (4 Punkte)

Je 1 Punkt:

  1. Es wird geprüft, ob zum Rechner shop.csb-rad.example eine TCP-Verbindung auf Port 443 (HTTPS) aufgebaut werden kann.

  2. Die IP-Adresse, in die der Name per DNS aufgelöst wurde (Adresse des Shop-Servers).

  3. Die eigene (lokale) IP-Adresse, von der aus die Verbindung aufgebaut wurde – Jonas’ Rechner im Büronetz.

  4. Der TCP-Verbindungsaufbau zu Port 443 war erfolgreich: Auf dem Server nimmt ein Dienst auf diesem Port Verbindungen an.

gb) Erläutere, warum dieser Test nicht ausreicht, um die Funktion des Shops zu überwachen, und beschreibe eine bessere Prüfung. (3 Punkte)

  • TcpTestSucceeded zeigt nur, dass der Port erreichbar ist (der Webserver bzw. Lastverteiler nimmt Verbindungen an). Ob die Anwendung dahinter funktioniert – Shop-Software, Datenbank, Zahlungsanbindung –, wird nicht geprüft. Hier antwortet der Webserver zwar, liefert aber den Fehler 503 (2 Punkte).

  • Besser: eine echte HTTP(S)-Anfrage an den Shop oder an eine eigene Statusseite stellen (z. B. Invoke-WebRequest, curl) und den Statuscode 200 sowie einen erwarteten Inhalt (z. B. einen Text der Startseite oder "status": "ok" einer Statusseite, die auch die Datenbank prüft) auswerten; gegebenenfalls die Antwortzeit messen (1 Punkt).

gc) Ein Überwachungsskript soll jede Minute den HTTP-Statuscode des Shops abfragen. Erst wenn drei Abfragen nacheinander fehlschlagen, soll einmal ein Alarm verschickt werden. Sobald der Shop wieder antwortet, beginnt die Zählung neu. Ergänze die Lücken (1) bis (5) im Pseudocode. (6 Punkte)

fehler: integer := 0
alarmGesendet: boolean := false
wiederhole
    status := httpStatus("https://shop.csb-rad.example/status")
    wenn status = 200 dann
        (1) ..................................
        (2) ..................................
    sonst
        (3) ..................................
        wenn (4) ................................ dann
            sendeAlarm("Shop seit " & fehler & " Minuten gestört")
            (5) ..................................
        Ende wenn
    Ende wenn
    warte(60 Sekunden)
solange true
Lösung der Lücken im Überwachungsskript
Lücke Inhalt Punkte
(1) fehler := 0 1
(2) alarmGesendet := false 1
(3) fehler := fehler + 1 1
(4) fehler >= 3 und nicht alarmGesendet 2
(5) alarmGesendet := true 1
  1. und (2) dürfen vertauscht sein. Statt >= 3 ist auch = 3 richtig – dann ist alarmGesendet streng genommen überflüssig, schadet aber nicht.

Probe mit der Folge 200, 503, 503, 503, 503, 503, 200, 503, 503, 200, keine Antwort, keine Antwort, keine Antwort (eine Abfrage je Minute): Alarm in Minute 4 und Minute 13, sonst keiner.

h) Die Skripte sollen sicher betrieben werden.

ha) Cem Sarıkaya bildet von jedem freigegebenen Skript einen SHA-256-Hashwert und legt ihn getrennt vom Skript ab. Erläutere, wozu das dient. (3 Punkte)

  • Ein Hashwert ist eine Prüfsumme fester Länge, die sich schon bei der kleinsten Änderung der Datei vollständig ändert (1 Punkt).

  • Bildet man vor dem Start (oder regelmäßig) den Hashwert erneut und vergleicht ihn mit dem hinterlegten Wert, erkennt man jede Veränderung des Skripts – absichtlich (Manipulation, z. B. eingeschleuster Schadcode in einem Skript, das mit hohen Rechten läuft) oder versehentlich (1 Punkt).

  • Der Wert muss getrennt abgelegt werden, weil jemand, der das Skript verändert, sonst auch den Hashwert anpassen könnte. Noch besser ist eine digitale Signatur, die zusätzlich zeigt, wer das Skript freigegeben hat (1 Punkt).

hb) Auf dem neuen Verwaltungsrechner startet das PowerShell-Skript nicht, weil die Ausführungsrichtlinie (execution policy) nur signierte Skripte zulässt. Jonas will die Richtlinie dauerhaft auf Unrestricted stellen. Nimm Stellung. (3 Punkte)

Stellungnahme mit Begründung, zum Beispiel:

  • Dagegen: Mit Unrestricted laufen alle Skripte, auch heruntergeladene oder per E-Mail eingeschleuste. Die Richtlinie ist kein vollständiger Schutz, verhindert aber, dass Benutzer*innen versehentlich fremde Skripte starten (1 Punkt).

  • Besser: die eigenen Skripte mit einem Codesignatur-Zertifikat signieren und die Richtlinie AllSigned beibehalten, oder mindestens RemoteSigned (lokale Skripte laufen, aus dem Internet geladene nur signiert) (1 Punkt).

  • Für geplante Aufgaben lässt sich das Skript gezielt freigeben, ohne die Richtlinie für den ganzen Rechner zu lockern; die Entscheidung gehört in die Sicherheitsrichtlinie von Cem Sarıkaya (1 Punkt).

hc) Nenne zwei Regeln für den Umgang mit Zugangsdaten in Skripten. (2 Punkte)

Je 1 Punkt, zum Beispiel:

  • Keine Kennwörter im Klartext im Quelltext (der Quelltext landet in Sicherungen, Versionsverwaltung und Tickets) – stattdessen Tresor für Zugangsdaten, sicher gespeicherte Anmeldeinformationen oder Abfrage beim Start.

  • Skripte unter einem eigenen Dienstkonto mit minimalen Rechten ausführen, nicht unter einem persönlichen Administratorkonto oder einem gemeinsam genutzten Konto wie admin.

  • Für SSH-Verbindungen (z. B. Kopie auf einen zweiten Standort) ein Schlüsselpaar mit einem modernen Verfahren wie Ed25519 statt eines Kennworts verwenden; den privaten Schlüssel schützen.

  • Startkennwörter für neue Konten nicht ins Skript schreiben, sondern zufällig erzeugen oder beim Start abfragen, und beim ersten Anmelden eine Änderung erzwingen.

  • Kennwörter nie in Protokolldateien oder Bildschirmausgaben schreiben.

Gesamtpunktzahl Aufgabe 2.1: 97 Punkte (a: 8, b: 26, c: 13, d: 14, e: 6, f: 9, g: 13, h: 8).


Hinweis zur Nachnutzung als Open Educational Resource (OER)

Dieser Artikel und seine Texte, Bilder, Grafiken, Code und sonstiger Inhalt sind - sofern nicht anders angegeben - lizenziert unter CC BY 4.0. Nennung gemäß TULLU-Regel bitte wie folgt: “Übungsaufgaben zu Skripten zur Systemautomatisierung” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/skripte-uebungsaufgaben veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/it-systeme/administration. Erstellt mit KI-Assistenz; von mir umfassend redaktionell überarbeitet, geprüft und freigegeben. Stand: 28.10.2026.

[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]

Kommentare gerne per Mastodon, Verbesserungsvorschläge per gitlab issue (siehe oben). Beitrag teilen: