GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Verwenden von Bash für die Automatisierung

Angesichts der jüngsten Flut von Artikeln zu den grundlegenden Aspekten von Bash (aufgelistet am Ende dieses Artikels) ist es unvermeidlich, dass einer Ihrer neuen Kollegen einen in die Cloud wirft. In Anbetracht dieser Dinge sind die nächsten logischen Schritte:

  1. Dass etwas absolut Kritisches überprüft werden kann, hängt davon ab, ob das "Cloud"-Skript einwandfrei funktioniert.
  2. Verifizieren Sie den ursprünglichen Autor des Skripts, der völlig vergessen hat, wie es tatsächlich funktioniert.
  3. Vergewissern Sie sich, dass der neueste Administrator beauftragt wurde, es ohne jegliche Validierung grundlegend zu ändern.

In diesem Artikel unterstütze ich alle Administratoren und helfe Ihnen, alle oben genannten Fehler zu vermeiden. Das Ergebnis wiederum führt zu einem zufriedeneren Management und hoffentlich zu unserer weiteren Beschäftigung.

Wie schreibt man "Bash script"

Um erleuchtet zu werden (und für Ihre Liebe zu $DEITY), checken Sie Ihren Code in ein Source Code Management (SCM) Tool ein. Verwenden Sie bereits während des Lernens ein lokales Repository als Spielplatz. Mit dieser Übung können Sie nicht nur Ihre Bemühungen im Laufe der Zeit protokollieren, sondern auch Fehler leicht rückgängig machen. Es gibt viele wunderbare Artikel über die ersten Schritte mit git die ich sehr empfehlen kann.

Ein kurzer Hinweis zum Verwenden und Erlernen von Bash, denn diese Skriptsprache bringt eine einzigartige Reihe von -ismen und stilistischen Autorenpräferenzen mit sich:Sobald Sie etwas entdecken, das Ihnen neu erscheint (Syntax, Stil oder ein Sprachkonstrukt), schlagen Sie es sofort nach . Verbringen Sie Ihre Zeit damit, das neue Element aus der Manpage (erste Wahl) oder dem Advanced Bash Scripting Guide (beide können offline aufgerufen werden) zu verstehen. Dadurch werden Sie zunächst langsamer, aber mit der Zeit wird Ihnen diese Übung dabei helfen, Ihr Wissen darüber zu erweitern, wo Sie Antworten finden.

Wiederverwendbare Bash-Nuggets als Bibliotheken schreiben

Skripte in der Automatisierung werden am besten unter Berücksichtigung der Unix-Philosophie geschrieben:Viele kleine Tools, die nur eine Sache tun. Das bedeutet, dass Sie beim Schreiben kleiner, spezialisierter Skripte und Bibliotheken viel besser abschneiden als mit einer riesigen „Küchenspüle“. Obwohl ich zugegebenermaßen ein paar Ungetüme geschrieben und gewartet habe (gelegentlich erfüllen sie einen Zweck).

Automatisierungsskripte müssen häufig von mehr als einem Autor verständlich und wartbar sein. Da viele kleine Skripte herumfliegen (und in der Versionskontrolle verfolgt werden), werden Sie schnell feststellen, dass Sie einen Verweis auf Werte für Namen, Versionen, Pfade oder URLs teilen müssen. Das Schreiben dieser gemeinsamen Elemente in Bibliotheken bietet auch zusätzlichen mentalen Raum für Betreuer, um die Inline-Dokumentation zu schätzen. Darüber hinaus macht dies die Nutzung von Komponententests nahezu trivial (wir werden uns am Ende mit diesem Thema befassen).

Lassen Sie uns von Anfang an eine gute Codehygiene praktizieren, indem Sie ein lokales „Play“-Repository erstellen. Erstellen Sie in Ihrem neuen Repository einen Ort für unsere Skripts und Bibliotheksdateien. Der Einfachheit halber halte ich mich gerne an die gut verstandenen FHS-Standards. Erstellen Sie die Verzeichnisse ./bin/ und ./lib/ im Stammverzeichnis des Repositorys. In einem größeren Automatisierungsprojekt würde ich diese Namen immer noch verwenden, aber sie könnten tief vergraben sein (z. B. unter einem scripts oder tools Unterverzeichnis).

Wenn ich über Pfade spreche, komme ich zu einem großartigen Thema für eine erste Bibliothek. Wir brauchen eine Möglichkeit für unsere zukünftigen Komponenten, Strukturelemente und übergeordnete Werte zu referenzieren. Erstellen Sie mit Ihrem bevorzugten Editor die Datei ./lib/anchors.sh und fügen Sie den folgenden Inhalt hinzu:



# A Library of fundamental values
# Intended for use by other scripts, not to be executed directly.

# Set non-'false' by nearly every CI system in existence.
CI="${CI:-false}"  # true: _unlikely_ human-presence at the controls.
[[ $CI == "false" ]] || CI='true'  # Err on the side of automation

# Absolute realpath anchors for important directory tree roots.
LIB_PATH=$(realpath $(dirname "${BASH_SOURCE[0]}"))
REPO_PATH=$(realpath "$LIB_PATH/../")  # Specific to THIS repository
SCRIPT_PATH=$(realpath "$(dirname $0)")

Die Datei beginnt mit zwei Leerzeilen, und der erste Kommentar erklärt, warum. Die Bibliothek sollte nicht als ausführbar festgelegt werden (trotz des Namens, der auf .sh endet, was auf seinen Typ hinweist). Wenn die Bibliothek direkt ausgeführt wurde, ist es möglich, dass die Shell des Benutzers verschwindet (oder Schlimmeres). Direkte Ausführung deaktivieren (chmod -x ./lib/anchors.sh ) ist die erste Stufe des Administratorschutzes für Anfänger. Der Kommentar am Anfang der Datei ist die zweite Ebene.

Konventionsgemäß sollten Kommentare immer das Warum beschreiben (nicht das was ) der folgenden Aussagen. Ein Leser kann die Aussage einfach lesen, um zu verstehen, was sie tut, aber er kann nicht zuverlässig erahnen, was der Autor zu diesem Zeitpunkt dachte. Bevor ich jedoch tiefer eingehe, muss ich auf ein Problem eingehen, das die Leute bei Bash oft überrascht.

Die Bash-Standardwerte liefern beim Verweis auf eine undefinierte Variable einen leeren String. Das CI Variable (oder etwas Analoges in Ihrer Automatisierung) soll die wahrscheinliche Abwesenheit eines Menschen anzeigen. Leider müssen Menschen für die Roboter-Overlords das Skript wahrscheinlich mindestens einmal manuell ausführen. An dieser Stelle vergessen sie wahrscheinlich, einen Wert für CI festzulegen bevor Sie die Eingabetaste drücken.

Wir müssen also einen Standardwert für den Wert festlegen und sicherstellen, dass er immer entweder wahr oder falsch ist. Der obige Beispielbibliothekscode zeigt sowohl, wie man testet, ob eine Zeichenfolge leer ist, als auch, wie man erzwingt, dass die Zeichenfolge einen Wert aus einem Wertepaar enthält. So wie ich die erste Gruppe von Anweisungen in anchors.sh lese ist:

Definieren Sie 'CI ' vorwärts als Ergebnis von:

  1. Untersuchen des vorherigen Werts von CI (es kann undefiniert sein, also ein leerer String).
  2. Der ':- ' Teil bedeutet:
    1. Wenn der Wert ein leerer String war, stellen Sie den String 'false dar ' stattdessen.
    2. Wenn der Wert keine leere Zeichenfolge war, verwenden Sie, was immer es war (einschließlich 'DaRtH VaDeR ').

Testen Sie das Ding in '[[ ' und ' ]] ':

  1. Falls der neue Wert von 'CI ' entspricht der Literalzeichenfolge "false ", geben Sie den Exit-Code 0 aus (bedeutet Erfolg oder Wahrheit).
  2. Andernfalls geben Sie den Exit-Code 1 aus (bedeutet Versagen oder Unwahrheit)

Wenn der Test mit 0 beendet wurde , fahren Sie mit der nächsten Zeile fort, oder (die ' || ' Teil), nehmen Sie an, dass ein unerfahrener Administrator CI=YES!PLEASE gesetzt hat oder ein Perfect-Machine-Set CI=true . Setzen Sie sofort den Wert von 'CI ' in die Literalzeichenfolge 'true '; Weil perfekte Maschinen besser sind, machen Sie keinen Fehler.

Für den Rest dieser Bibliothek sind die Ankerpfadwerte fast immer nützlich in Skripten, die in Automatisierung aus einem Repository ausgeführt werden. Bei Verwendung in einem größeren Projekt müssten Sie den relativen Speicherort des Bibliotheksverzeichnisses in Bezug auf das Repository-Stammverzeichnis anpassen. Andernfalls belasse ich es beim Verständnis dieser Aussagen als Rechercheübung für den Leser (tun Sie es jetzt, es wird Ihnen später helfen).

Bash-Bibliotheken in Bash-Skripten verwenden

Verwenden Sie zum Laden einer Bibliothek die source eingebauter Befehl. Dieser Befehl ist nichts Besonderes. Geben Sie ihm den Speicherort einer Datei, und dann liest und führt es den Inhalt genau dort und dann aus, was bedeutet, dass der Laufzeit-Kontext des Bibliothekscodes wird tatsächlich das Skript sein, das source ist d es.

Um zu verhindern, dass Ihnen zu viel Gehirn aus den Ohren tropft, finden Sie hier eine einfache ./bin/example.sh Skript zur Veranschaulichung:

#!/bin/bash

LIB_PATH="$PWD/$(dirname $0)/../lib/anchors.sh"
echo "Before loading: $LIB_PATH"
set -ax
cd /var/tmp
source $LIB_PATH
echo "After loading: $(export -p | grep ' LIB_PATH=')"

Möglicherweise bemerken Sie sofort, dass das Skript den Laufzeitkontext geändert hat, bevor die Bibliothek geladen wurde. Es definiert auch LIB_PATH lokal und verweist auf eine Datei (verwirrenderweise statt auf ein Verzeichnis) mit einem relativen Pfad (zur Veranschaulichung).

Führen Sie dieses Skript aus und untersuchen Sie die Ausgabe. Beachten Sie, dass alle Operationen in der Bibliothek anchors.sh lief in /var/tmp/ Verzeichnis und exportiert automatisch seine Definitionen. Die alte Definition für LIB_PATH wurde von a verprügelt und exportiert im set -ax . Diese Tatsache ist in der Ausgabe von declare -x sichtbar aus dem export kommen Befehl. Hoffentlich ist die Debugging-Ausgabe (der x im set -ax ) ist verständlich.

Wenn Sie auf diese Weise debuggen, gibt Bash alle Zwischenwerte aus, wenn jede Zeile analysiert wird. Ich habe dieses Skript hier eingefügt, um zu zeigen, warum Sie niemals set -ax möchten oder wechseln Sie Verzeichnisse mit den Befehlen aus der obersten Ebene einer Bibliothek. Denken Sie daran, dass Bibliotheksanweisungen zur Ladezeit im Skript ausgewertet werden. Das Ändern der Umgebung in einer Bibliothek führt also zu Laufzeitnebenwirkungen in dem Skript, das source verwendet um es zu laden. Nebenwirkungen wie diese treiben garantiert mindestens einen Systemadministrator in den Wahnsinn. Man weiß nie, dieser Administrator könnte ich sein, also tu es nicht.

Betrachten Sie als praktisches Beispiel eine imaginäre Bibliothek, die eine Funktion definiert, die eine Benutzername/Passwort-Umgebungsvariable verwendet, um auf einen entfernten Dienst zuzugreifen. Wenn die Bibliothek ein set -ax auf oberster Ebene ausgeführt hat vor dieser Funktion, dann wird jedes Mal, wenn sie geladen wird, die Debugging-Ausgabe die Anzeige dieser Variablen beinhalten und Ihre Geheimnisse überall für alle sichtbar verspritzen. Schlimmer noch, es wird (aus der Sicht eines aufrufenden Skripts) für einen unerfahrenen Kollegen schwierig sein, die Ausgabe zu deaktivieren, ohne seine Tastatur anzubrüllen.

Abschließend ist es wichtig, sich bewusst zu machen, dass Bibliotheken im Kontext ihres Aufrufers "abspielen". Dieser Faktor ist auch der Grund für das Beispiel anchors.sh verwenden können  $0   (Pfad und Dateiname des ausführbaren Skripts), aber der Pfad zur Bibliothek selbst ist nur über den "magischen" '${BASH_SOURCE[0]}' verfügbar (Array-Element). Dieser Faktor mag zunächst verwirrend sein, aber Sie sollten versuchen, diszipliniert zu bleiben. Vermeiden Sie breite, weitreichende Befehle in Bibliotheken. Wenn Sie das tun, werden alle neuen Admin-Mitarbeiter darauf bestehen, für Ihre Donuts zu bezahlen.

Einheitentests für Bibliotheken schreiben

Das Schreiben von Komponententests kann sich wie eine entmutigende Aufgabe anfühlen, bis Sie feststellen, dass eine perfekte Abdeckung normalerweise Zeitverschwendung ist. Es ist jedoch eine gute Angewohnheit, Ihren Testcode immer zu verwenden und zu aktualisieren, wenn Sie Ihren Bibliothekscode berühren. Das Ziel des Testschreibens ist es, sich um die häufigsten und offensichtlichsten Anwendungsfälle zu kümmern und dann weiterzumachen. Achten Sie nicht besonders auf Sonderfälle oder weniger als die ganze Zeit. Ich schlage auch vor, Ihre Bemühungen zum Testen von Bibliotheken zunächst auf die Ebene der Komponententests zu konzentrieren, anstatt auf Integrationstests.

Schauen wir uns ein weiteres Beispiel an:das ausführbare Skript ./lib/test-anchors.sh :

#!/bin/bash

# Unit-tests for library script in the current directory
# Also verifies test script is derived from library filename

TEST_FILENAME=$(basename $0)  # prefix-replace needs this in a variable
SUBJ_FILENAME="${TEST_FILENAME#test-}"; unset TEST_FILENAME
TEST_DIR=$(dirname $0)/

ANY_FAILED=0

# Print text after executing command, set ANY_FAILED non-zero on failure
# usage: test_cmd "description" <command> [arg...]

test_cmd() {
   local text="${1:-no test text given}"
   shift
   if ! "$@"; then
      echo "fail - $text"; ANY_FAILED=1;
   else
      echo "pass - $text"
   fi
}

test_paths() {
   source $TEST_DIR/$SUBJ_FILENAME
   test_cmd "Library $SUBJ_FILENAME is not executable" \
      test ! -x "$SCRIPT_PATH/$SUBJ_FILENAME"
   test_cmd "Unit-test and library in same directory" \
      test "$LIB_PATH" == "$SCRIPT_PATH"
   for path_var in LIB_PATH REPO_PATH SCRIPT_PATH; do
      test_cmd "\$$path_var is defined and non-empty: ${!path_var}" \
         test -n "${!path_var}"
      test_cmd "\$$path_var referrs to existing directory" \
         test -d "${!path_var}"
   done
}

# CI must only/always be either 'true' or 'false'.
# Usage: test_ci <initial value> <expected value>

test_ci() {
   local prev_CI="$CI"  # original value restored at the end
   CI="$1"
   source $TEST_DIR/$SUBJ_FILENAME
   test_cmd "Library $SUBJ_FILENAME loaded from $TEST_DIR" \
      test "$?" -eq 0
   test_cmd "\$CI='$1' becomes 'true' or 'false'" \
      test "$CI" = "true" -o "$CI" = "false"
   test_cmd "\$CI value '$2' was expected" \
      test "$CI" = "$2"
   CI="$prev_CI"
}

test_paths
test_ci "" "false"
test_ci "$RANDOM" "true"
test_ci "FoObAr" "true"
test_ci "false" "false"
test_ci "true" "true"

# Always run all tests and report, exit non-zero if any failed

test_cmd "All tests passed" \
   test "$ANY_FAILED" -eq 0
[[ "$CI" == "false" ]] || exit $ANY_FAILED  # useful to automation
exit(0)

Der Grund, warum ich dieses Skript in ./lib eingefügt habe (im Gegensatz zu ./bin ) ist sowohl aus Bequemlichkeit als auch weil sich Tests niemals auf die Verwendung des Codes verlassen dürfen, den sie überprüfen. Da dieser Test Pfade überprüfen muss, ist es einfacher, ihn im selben Pfad wie die Bibliothek abzulegen. Ansonsten ist dieser Ansatz eine Frage der persönlichen Präferenz. Fühlen Sie sich frei, den Test jetzt auszuführen, da er Ihnen helfen könnte, den Code zu verstehen.

Abschluss

Dieser Artikel stellt keineswegs die Gesamtheit der Verwendung von Bash in der Automatisierung dar. Ich habe jedoch versucht, grundlegende Kenntnisse und Empfehlungen zu vermitteln, die (wenn sie befolgt werden) zweifellos Ihr Leben erleichtern werden. Dann, selbst wenn die Dinge schwierig werden, ist ein solides Verständnis der Bedeutung des Laufzeitkontexts praktisch.

Schließlich kann das Scripting in oder für die Automatisierung Fehler unverzeihlich machen. Selbst grundlegende Komponententests für Ihre Bibliotheken zu haben, wird sowohl Vertrauen aufbauen als auch der nächsten Person helfen, die kommt (was Sie nach fünf Jahren des Vergessens sein könnten). Den gesamten in diesem Artikel verwendeten Beispielcode finden Sie online hier.

Möchten Sie Ihre Bash-Grundlagen auffrischen? Siehe:

  • Wie man mit Bash programmiert:Logische Operatoren und Shell-Erweiterungen
  • 5 Möglichkeiten zur Verbesserung Ihrer Bash-Skripte
  • Erste Schritte mit Shell-Scripting
  • 10 grundlegende Linux-Befehle, die Sie kennen müssen
  • Tipps und Tricks zu Linux-Umgebungsvariablen

[ Möchten Sie Red Hat Enterprise Linux ausprobieren? Laden Sie es jetzt kostenlos herunter. ]


Linux
  1. Fehlerbehandlung in Bash-Skripten

  2. Verwenden Sie die Erweiterung .sh oder .bash für Bash-Skripte?

  3. Verwenden des Linux-Sleep-Befehls in Bash-Skripten

  4. Verwenden des Linux-Basisnamenbefehls in Bash-Skripten

  5. Automatisierung der Telnet-Sitzung mit Bash-Skripten

Tipps zur Verwendung von tmux

Tipps zur Verwendung des Bildschirms

Shell-Scripting für Anfänger – So schreiben Sie Bash-Scripts unter Linux

Verwendung von „${a:-b}“ für die Variablenzuweisung in Skripten?

Verwenden des Linux-Dirname-Befehls in Bash-Skripten

Verwenden des Bash-printf-Befehls zum Drucken formatierter Ausgaben