Bash – mal anders: Einstieg ins Scripting ohne Overkill

Die wichtigsten Bash-Grundlagen, kompakt erklärt: Shebang, Rechte, Variablen, Input, Bedingungen, Schleifen und Arrays für echte Automationsaufgaben

Dieser Artikel hat eine Lesedauer von 6 minutes Minuten.

Bash – mal anders: Einstieg ins Scripting ohne Overkill

Ein kleines Bash-Skript spart Routinearbeit. Dieser Einstieg bleibt pragmatisch und zeigt Muster, die im Alltag wirklich helfen.

Worum es hier geht und was Bash besonders macht

Wenn du bereits erfahrung hast mit Programmieren kannst du direkt zum Kapitel Shebang: Die erste Zeile entscheidet, wie dein Skript startet springen.

Bash ist kein “Programmieren wie in einer App”, sondern oft Automatisierung in kleinen Schritten: Dateien anfassen, Text ausgeben, Eingaben verarbeiten, Tools kombinieren. Das wirkt simpel, hat aber Eigenheiten, die man früh verstehen sollte.

Zwei Punkte, die später viel Ärger sparen:

  • Bash ist tolerant gegenüber Fehlern: Ein einzelner Fehler stoppt nicht automatisch das ganze Skript.
  • Variablen sind standardmäßig global: Ohne bewusstes Gegensteuern “leaken” Werte leicht in andere Teile des Skripts.

Der Rest dieses Artikels ist ein Werkzeugkasten: kurze Erklärungen, konkrete Beispiele, und am Ende ein kleines, zusammengesetztes Mini-Skript.

Schritt 0

Bevor wir überhaupt über Bash sprechen, brauchen wir drei Dinge:

  1. eine Datei, in die das Skript kommt
  2. ein Terminal, um es auszuführen
  3. einen Editor/eine IDE

Auf macOS ist das naheliegendste Terminal die App “Terminal” (terminal.app). Wer mehr auf KI steht, für den gibt es Terminal Wrap, das aber erst heruntergeladen werden muss.

Ich würde der Einfachheit halber das direkt in VS Code integrierte Terminal empfehlen. Für das Tutorial ist das egal. Wichtig ist nur, dass du Kommandos eintippen und Dateien im richtigen Ordner ausführen kannst.

Lege dir einen Arbeitsordner an, wechsle hinein und erstelle dort eine neue Datei, zum Beispiel helloworld.sh:

mkdir -p ~/bash-tutorial
cd ~/bash-tutorial
touch helloworld.sh

Wenn du lieber direkt in einem Editor startest, kannst du die Datei auch mit VS Code öffnen:

code helloworld.sh

Der entscheidende Punkt ist das Verzeichnis: Du solltest im Terminal dort stehen, wo deine Datei liegt. Ein schneller Check ist ls, damit sie wirklich sichtbar ist:

ls -la

Shebang: Die erste Zeile entscheidet, wie dein Skript startet

Das “Shebang” steht in Zeile 1 und beginnt mit #!. Danach kommt der Pfad zum Interpreter.

Beispiel für Bash:

#!/bin/bash

Der praktische Effekt: Du musst Bash nicht jedes Mal explizit davor schreiben. Statt bash script.sh kannst du das Skript direkt ausführen, sofern es die passenden Rechte hat.

Ein häufig robusterer Ansatz ist env, wenn du dich nicht auf einen festen Pfad verlassen willst:

#!/usr/bin/env bash

Das ist besonders dann sinnvoll, wenn Bash nicht an derselben Stelle liegt oder wenn du in Teamprojekten auf unterschiedlichen Systemen arbeitest.

Rechte: Warum “Permission denied” auftaucht

Ein Skript ist nur dann direkt ausführbar, wenn es das Execute-Recht hat. Das setzt du mit chmod:

chmod +x helloworld.sh

Danach kannst du es so starten:

./helloworld.sh

Mit ls -al siehst du die Rechte einer Datei. Das x in der Rechte-Spalte ist das Signal, dass sie ausführbar ist.

Ausgabe: echo, Strings und Escape-Sequenzen

Der schnellste Output ist echo:

echo "Hello World"

Escape-Sequenzen wie \n werden nicht immer automatisch interpretiert. Mit -e klappt es in vielen Setups:

echo -e "Erste Zeile\nZweite Zeile"

Wenn du planbar formatieren willst, ist printf oft die stabilere Wahl:

printf "Erste Zeile\nZweite Zeile\n"

Variablen: Zuweisung, Nutzung, Veränderlichkeit

Variablen werden ohne Typdeklaration zugewiesen. Wichtig ist: keine Leerzeichen um das =.

age=32
echo "Hi, I am $age years old"

Variablen kannst du später überschreiben:

age=32
echo "Zuerst: $age"
age=100
echo "Dann: $age"

Für Werte, die sich nicht verändern sollen, gibt es readonly:

readonly pi=3.14
echo "Pi: $pi"
# pi=3.2  # würde einen Fehler auslösen

Das ist ein guter Schutz gegen versehentliche Änderungen, gerade in längeren Skripten.

Kommentare: Lesbarkeit statt Lärm

Einzeilige Kommentare beginnen mit #:

# Das ist ein Kommentar
echo "Code läuft"

Mehrzeilige Kommentare sind in Bash kein “offizielles” Sprachfeature. Ein häufig genutztes Muster ist ein No-Op-Block, der vom Interpreter ignoriert wird:

: '
Dieser Block wird nicht ausgeführt.
Er ist praktisch für Notizen oder Debugging.
'

Kommentar-Regel für den Alltag: Kommentiere vor allem “Warum”, nicht “Was”. Was echo macht, muss selten erklärt werden. Warum etwas in genau dieser Reihenfolge passiert, dagegen schon.

Parameter: Dein Skript wird dynamisch

Bash stellt Positionsparameter bereit:

  • $0 Name des Skripts
  • $1, $2, … Argumente
  • $# Anzahl der Argumente
  • $@ alle Argumente (als Liste)
  • $$ Prozess-ID
  • $? Exit-Status des letzten Befehls

Beispiel:

#!/usr/bin/env bash

echo "Skript: $0"
echo "Erstes Argument: $1"
echo "Anzahl: $#"
echo "Alle: $@"
echo "PID: $$"
echo "Exit Status: $?"

Aufruf:

./params.sh 10 20 Oliver

Bedingungen: if, Vergleich und saubere Syntax

Die if-Struktur endet mit fi. In Bedingungen sind Leerzeichen entscheidend.

Stringvergleich:

#!/usr/bin/env bash

if [[ "$1" == "Hello" ]]; then
  echo "Hello World"
else
  echo "Goodbye World"
fi

Wichtig: [[ ... ]] ist in Bash meist die bessere Wahl als [ ... ], weil es weniger Stolperfallen bei Leerzeichen und Pattern-Matching hat.

PS: Du kannst z. B. if nutzen, um zu prüfen, ob ein Directory existiert. Mehr dazu in diesem Blogpost.

Regex-Checks: “Standard Library” per Muster und Konvention

Bash hat keine komfortable Typprüfung wie viele Hochsprachen. Du kannst aber mit Regex in [[ ... ]] arbeiten.

Nur Buchstaben:

value="$1"

if [[ "$value" =~ ^[A-Za-z]+$ ]]; then
  echo "Nur Buchstaben"
else
  echo "Nicht nur Buchstaben"
fi

Integer:

value="$1"

if [[ "$value" =~ ^[0-9]+$ ]]; then
  echo "Integer"
else
  echo "Kein Integer"
fi

Für echte Validierung in Produktionsskripten lohnt sich oft ein kleiner, klarer “Fail fast”-Block mit verständlicher Fehlermeldung.

Eingabe: read, Prompt und versteckte Eingaben

Interaktive Eingabe geht mit read.

Mit Prompt:

read -p "What is your name? " name
echo "Hello $name"

Ohne Prompt, dafür besser kontrollierbar im Layout:

echo "What is your name?"
read name
echo "Hello $name"

Versteckte Eingabe, zum Beispiel für sensible Werte:

read -s -p "Secret: " secret
echo
echo "Danke"

Warnhinweis: Versteckte Eingaben sind ein legitimes Feature, werden aber auch für unseriöse Zwecke missbraucht. Nutze es transparent und sparsam, und speichere solche Werte nicht leichtfertig weiter.

Schleifen: Range, C-Style und der Scope-Haken

Einfache Range-Variante:

for i in {0..5}; do
  echo "i=$i"
done

C-Style:

for ((i=0; i<=5; i++)); do
  echo "i=$i"
done

Typischer Bash-Haken: Variablen aus der Schleife existieren danach weiter.

for i in {0..3}; do
  :
done

echo "Nach der Schleife ist i noch da: $i"

Das ist nicht “falsch”, aber man muss es wissen, sonst entstehen unerwartete Abhängigkeiten.

Arrays: Werte sammeln und durchgehen

Ein Array definierst du mit runden Klammern:

fruits=("Apfel" "Banane" "Kirsche" 100)

Zugriff per Index:

echo "${fruits[0]}"
echo "${fruits[1]}"

Iteration über Indizes:

for idx in "${!fruits[@]}"; do
  echo "Index $idx: ${fruits[$idx]}"
done

Ein praktisches Muster: Dateien einsammeln.

files=( *.sh )
echo "Alle Skripte: ${files[@]}"
echo "Anzahl: ${#files[@]}"

Hinweis: Wenn kein Match existiert, kann *.sh als Literal im Array landen. Für robuste Skripte lohnt sich ein Check, ob die erste “Datei” wirklich existiert.

Switch Cases: case als lesbare Alternative zu if-Ketten

case ist gut, wenn du viele Optionen sauber abbilden willst:

read -p "Option (1-3): " number

case "$number" in
  1) echo "Eins" ;;
  2) echo "Zwei" ;;
  3) echo "Drei" ;;
  *) echo "Ungültige Option" ;;
esac

Associative Arrays: Key-Value statt Index

Associative Arrays funktionieren ab Bash 4.

declare -A capitals
capitals["Deutschland"]="Berlin"
capitals["Frankreich"]="Paris"
capitals["Spanien"]="Madrid"

for country in "${!capitals[@]}"; do
  echo "$country: ${capitals[$country]}"
done

macOS-Hinweis: Auf manchen macOS-Systemen ist die vorinstallierte Bash historisch älter. Prüfe deine Version:

bash --version

Wenn du aktualisierst, achte darauf, was dein Standard-Shell ist und wie dein Terminal es startet. Ein Upgrade ist sinnvoll, aber nicht “gratis”, weil Shell-Umstellungen auch Nebenwirkungen haben können.

Funktionen: Wiederverwendung und Scope bewusst machen

Funktion definieren:

hello() {
  echo "Hello World"
}

hello

Parameter in Funktionen: Auch hier gelten Positionsparameter, aber bezogen auf die Funktion.

greet() {
  echo "Hello $1$2"
}

greet "Oliver" "!"

Wichtig in Bash: Variablen sind ohne local global.

set_name() {
  name="World"
}

set_name
echo "$name"

Sauberer ist:

set_name() {
  local name="World"
  echo "$name"
}

set_name
# echo "$name"  # bleibt leer oder unverändert

Arithmetik: Rechnen in Bash, ohne Umwege

Arithmetik klappt mit $(( ... )):

a=10
b=5
sum=$((a + b))
echo "$sum"

Compound Operatoren:

n=10
n+=10
echo "$n"

Inkrement und Dekrement:

x=1
x++
echo "$x"

x--
echo "$x"

Command Substitution: Tools kombinieren statt alles selbst bauen

Output eines Kommandos in eine Variable:

first_file=$(ls -1 | head -n 1)
echo "Erste Datei: $first_file"

Oder direkt inline:

echo "Dateien im Ordner: $(ls -1 | wc -l)"

Das ist ein Kernprinzip: Bash gewinnt nicht durch eine große Bibliothek, sondern durch das Zusammensetzen vorhandener Tools.

Ein Beispiel mit Sortierung ohne eigene Logik:

fruits=("Kirsche" "Apfel" "Banane")
sorted=$(printf "%s\n" "${fruits[@]}" | sort)
echo "$sorted"

Mini-Skript: Ein solides Gerüst für echte Nutzung

Zum Abschluss ein kleines Skript, das mehrere Konzepte verbindet: Shebang, Parameter, Input, Checks, Arrays, Schleifen und Exit Codes.

#!/usr/bin/env bash
set -u

usage() {
  echo "Usage: $0 [--name NAME] [--list-sh]"
}

name=""

if [[ $# -eq 0 ]]; then
  usage
  exit 1
fi

while [[ $# -gt 0 ]]; do
  case "$1" in
    --name)
      shift
      name="${1:-}"
      ;;
    --list-sh)
      list_sh="yes"
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      echo "Unbekannte Option: $1"
      usage
      exit 1
      ;;
  esac
  shift
done

if [[ -n "$name" ]]; then
  echo "Hello $name"
else
  read -p "What is your name? " name
  echo "Hello $name"
fi

if [[ "${list_sh:-}" == "yes" ]]; then
  files=( *.sh )
  if [[ "${files[0]}" == "*.sh" ]]; then
    echo "Keine .sh Dateien gefunden"
    exit 0
  fi

  echo "Gefundene .sh Dateien (${#files[@]}):"
  for f in "${files[@]}"; do
    echo " - $f"
  done
fi

exit 0

Zwei bewusste Designentscheidungen:

  • set -u hilft, Tippfehler bei Variablennamen früh zu erkennen, weil die Bash dann bei nicht gesetzten Variablen stoppt.
  • Die Option --help ist kein Luxus. Gerade bei kleinen Tools ist eine klare Nutzungserklärung die halbe Miete.

Wenn du Bash so angehst, wird es schnell vom “Hack” zum verlässlichen Werkzeug: kleine, lesbare Skripte, die echte Schritte automatisieren, ohne dass du dir dabei eine eigene Mini-Programmiersprache bauen musst.

Hier findest du noch meine 10 macOS-Terminal-Tools

Eine Idee im Kopf?


Dann kontaktier mich doch


Wenn du Social-Media-Unterstützung brauchst oder ein Thema journalistisch sauber aufbereiten willst, melde dich.