Text size
Ausprobiert & getestet6 Min. Lesezeit

Bash-Skripte, die dich nicht überraschen

Die Gewohnheiten, die Shell-Skripte sicher machen, und wann man auf Python wechselt.

Bash scripts that don't surprise you
KI-generierte Illustration

Bash ist überall in DevOps. CI-Pipelines, Docker-Dateien, Bereitstellungs-Skripte, Cron-Jobs, schnelle Automatisierungen. Es ist der Klebstoff, der die Infrastruktur zusammenhält.

Bash hat versteckte Gefahren. Standardmäßig läuft es immer nach Fehlern, behandelt undefinierte Variablen als leer und teilt Wörter auf unerwartete Weise. Ein Skript, das auf Ihrem Laptop gut funktioniert, könnte versehentlich den falschen Ordner in der Produktion löschen, wenn eine Variable leer ist.

Hier sind die Gewohnheiten, die meine Drehbücher langweilig machen, auf die beste Art und Weise.

Die ersten Zeilen jedes Drehbuchs

#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'

Was sie tun:

  • set -e: das Skript stoppen, wenn ein Befehl fehlschlägt, anstatt weiterzumachen.
  • set -u: behandeln undefinierte Variablen als Fehler, anstelle einer leeren Zeichenfolge.
  • set -o pipefail: eine Pipeline versagt, wenn irgendein Befehl darin versagt, nicht nur der letzte.
  • IFS=$'\n\t': nur auf Neulinien und Registerkarten, nicht auf Leerzeichen. Weniger Überraschungen mit Dateinamen mit Leerzeichen.

Warum? set -u Es ist so wichtig, in einem Beispiel:

rm -rf "$BUILD_DIR/"*

Wenn BUILD_DIR ist versehentlich ausgeschaltet, ohne set -u Das wird rm -rf /*- Ich weiß . Mit set -u, das Skript endet mit einem Fehler. Diese einzige Linie hat viele Systeme gerettet.

(set -e hat einige bekannte Randfälle, wie Befehle in Bedingungen oder Unterhallen. Es ist nicht perfekt, aber es ist eine große Verbesserung gegenüber dem Standardverhalten.)

Bash geht davon aus, dass du weißt, was du tust. Gute Drehbücher machen diese Annahme nicht über irgendjemanden, auch nicht über ihren Autor.

ShellCheck

ShellCheck ist ein Linter für Shell-Skripte, und das nützlichste Werkzeug, um bessere Bash zu schreiben. Es fängt nicht zitierte Variablen, falsche Vergleiche, nutzlose catDie Kommission hat die Kommission aufgefordert, die in den letzten Jahren vorgenommenen Maßnahmen zu prüfen.

shellcheck deploy.sh

Fügen Sie es Ihrem Redakteur und Ihrem Informanten hinzu. Ich behandle ShellCheck Warnungen wie Kompiliererfehler.

Gewohnheiten, die die meisten Käfer verhindern

Nennen Sie Ihre Variablen. - Immer. - Ich weiß. "$file"Nein . $file- Ich weiß . Nicht zitierte Variablen werden auf Leerzeichen aufgeteilt und als Wildcards ausgeweitet.

Verwendung [[ ]] für Prüfungen in Bash nicht [ ]- Ich weiß . Es ist sicherer und unterstützt mehr Funktionen.

if [[ -z "${ENVIRONMENT:-}" ]]; then
  echo "ENVIRONMENT must be set" >&2
  exit 1
fi

Vorgaben und erforderliche Kontrollen mit Parametererweiterung:

REGION="${REGION:-eu-central-1}"       # default value
: "${API_TOKEN:?API_TOKEN is required}" # fail with a message if unset

Verwendung local in Funktionen Also lecken keine Variablen.

Fehler auf stderr schreiben (>&2) und bei Ausfall mit Nicht-Null-Codes aussteigen.

Reinigen Sie mit trap, auch wenn das Skript fehlschlägt:

tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT

Verwendung $(...) statt von Backticks für die Kommandoersetzung.

Vermeiden Sie das Parsen ls. Verwenden Sie Kugeln oder find Stattdessen.

Eine Vorlage, die ich wiederverwende

#!/usr/bin/env bash
set -euo pipefail

usage() {
  cat <<EOF
Usage: $(basename "$0") -e <environment> [-d]
  -e  target environment (staging|production)
  -d  dry run
EOF
}

log() { printf '%s [%s] %s\n' "$(date -u +%FT%TZ)" "${1}" "${2}" >&2; }

main() {
  local env="" dry_run=false

  while getopts ":e:dh" opt; do
    case "$opt" in
      e) env="$OPTARG" ;;
      d) dry_run=true ;;
      h) usage; exit 0 ;;
      *) usage; exit 1 ;;
    esac
  done

  [[ "$env" =~ ^(staging|production)$ ]] || { usage; exit 1; }

  log INFO "Deploying to $env (dry run: $dry_run)"
  if [[ "$dry_run" == false ]]; then
    : # real work here
  fi
}

main "$@"

Es hat Argument-Parsing, Validierung, Logging mit Zeitstempeln, einen Trockenlaufmodus und eine main Funktion. Verstanden, löschen Sie, was Sie nicht brauchen.

Wenn Bash das falsche Werkzeug ist

Bash ist großartig, um Befehle zusammenzuhalten. Es ist schlecht in fast allem anderen. Ich wechsle auf Python (oder Go), wenn ein Skript:

  • Es ist länger als etwa 100 Zeilen.
  • Benötigt reale Datenstrukturen (eingebettet, Karten von Listen).
  • Parsiert JSON oder YAML über eine einfache jq Ruf an.
  • Spricht mit APIs mit Authentifizierung, Paginierung und erneuten Versuchen.
  • Er braucht eine subtilere Fehlerbearbeitung als "Stopp bei Versagen".
  • Er braucht Tests.
  • Es wird lange von vielen Menschen gepflegt.

Python mit einer ordnungsgemäßen CLI-Bibliothek oder einem kleinen Go-Tool ist an diesem Punkt viel einfacher zu lesen, zu testen und zu warten.

In CI-Pipelines

CI YAML enthält oft mehrzeilige Shell-Skripte. Es gelten die gleichen Regeln:

  • Viele CI-Systeme ermöglichen nicht set -euo pipefail - Sie ist standardmäßig. Fügen Sie es hinzu oder setzen Sie die Shell-Optionen für den gesamten Job.
  • Lange Skripte aus YAML in Dateien im Repository zu verschieben, wo ShellCheck sie lintet und Sie sie lokal ausführen können.

Was ist also das Einzige, was wir tun können?

Hinzufügen set -euo pipefail An der Spitze jedes Skripts und ShellCheck sie alle. Diese zwei Gewohnheiten verhindern die meisten Katastrophen, die ich von Shell-Skripten gesehen habe.

Ich denke, Bash ist ein scharfes Messer. Verwenden Sie es für kleine Schnitte, halten Sie es scharf mit ShellCheck, und wechseln Sie zu einem richtigen Werkzeug, wenn die Arbeit größer wird.

Desly