Das Beseitigen von Fehlern in selbst entwickelten Shell-Skripten kostet Zeit und Nerven. Spezielle Parameter für die Shell und das clevere Werkzeug Shellcheck helfen beim Debuggen.
Das Schreiben von Shell-Skripten macht Spaß und lässt Sie Kommandozeilenabläufe zusammenstellen, um diese in der Zukunft wiederzuverwenden. Damit müssen Sie sich nicht jedes Mal von Neuem die einzelnen Schritte überlegen. So automatisieren Sie wiederkehrende Abläufe, schaffen maßgeschneiderte Werkzeuge und erleichtern sich vor allem die tägliche Arbeit.
Allerdings lassen sich beim Entwickeln von Software Fehler kaum vermeiden. In der Regel gelingt es kaum im ersten Anlauf, alles perfekt und sauber zu programmieren – und das gilt für jedes proklamierte Paradigma zur Programmierung. Das Finden und Beheben der Fehler stellt jeden Entwickler aufs Neue vor Herausforderungen. Verschiedene Parameter der Bash und eine Reihe von Werkzeugen helfen Ihnen dabei, den eigenen Programmcode fehlerfrei(er) zu bekommen. Dazu braucht man nicht unbedingt hochkomplexe Entwicklungsumgebungen wie Eclipse.
Syntax-Check
Als typische Arbeitsweise für Shell-Entwickler gilt das wiederholte Starten des Skripts und das iterative Ausbügeln der dabei auftauchenden Fehler und Schwachstellen. Das klappt aber nicht immer: So wäre es beispielsweise hilfreich, schon vor dem Ausführen des Programmcodes zu wissen, ob die Syntax der Kommandos im Skript stimmt. Sofern noch nicht geschehen, aktivieren Sie dazu zunächst die Syntaxhervorhebung des Editors. In Vim erledigen Sie das beispielsweise über das Kommando :syntax on. Damit sehen Sie sofort anhand der Einfärbung des Programmcodes, wo Dinge schieflaufen, etwa weil schließende Klammern oder Anführungszeichen fehlen.
Syntax-Highlighting bei Gedit
Verwenden Sie den für Gnome typischen Editor Gedit, schalten Sie die Syntaxhervorhebung über das Menü Ansicht | Hervorhebungsmodus ein. Abbildung 1 zeigt das Auswahlmenü mit den von Gedit unterstützten Sprachdialekten dazu. Auch andere Linux-typische Editoren wie etwa Kate aus den KDE Applications bieten diese Funktion an.
Abbildung 2 zeigt ein kleines Shell-Skript, das die Systemlaufzeit über das Kommando uptime ausgibt. In Zeile 6 hat sich vor und hinter der Zuweisung mittels Gleichheitszeichen jeweils ein Leerzeichen eingeschlichen. Daher färbt Vim die Zuweisung anders ein als in der vorhergehenden Zeile 5.
Sieht mit der aktivierten Syntaxhervorhebung im Editor alles gut aus, folgt der nächste Schritt, der Syntax-Check. Die Bash hilft Ihnen dabei mit dem Schalter -n weiter: Darüber aktivieren Sie den internen Modus zum Syntax-Check. In diesem Modus führt die Shell die einzelnen Kommandos nicht aus, sondern überprüft lediglich, ob deren Syntax stimmt. Findet sie Fehler, gibt sie eine passende Fehlermeldung aus; ansonsten schweigt sie still vor sich hin. Listing 1 zeigt das für ein vergessenes oder falsch geschriebenes Schlüsselwort, im konkreten Beispiel für ein fehlendes Schleifenende.
Listing 1
$ bash -n while-passwd.sh
while-passwd.sh: line 22: syntax error: unexpected end of file.
Mitunter fallen solche Fehlermeldungen allerdings arg kryptisch aus und bedürfen einiges Hintergrundwissen, um sie richtig zu deuten und die entsprechenden Korrekturen vorzunehmen. Gerade für Einsteiger stellt das ein ernstzunehmendes Hindernis dar.
Shellcheck
An dieser Stelle kommt das Werkzeug Shellcheck ins Spiel. Das Programm analysiert Ihren Code auf recht strenge Art und Weise und liefert Ratschläge, um sowohl den Programmcode als auch den Programmierstil zu verbessern. Hübsch ist dabei, dass unter der jeweiligen Zeile noch ein (semi-)grafischer Hinweis steht, worüber sich Shellcheck gerade mokiert. Aus der Analyse in Listing 2 erkennen Sie beispielsweise, dass hier eine Schleife programmiert wurde, der ein korrektes Schleifenende fehlt: das Schlüsselwort done.
Listing 2
$ shellcheck while-passwd.sh
In while-passwd.sh line 12:
while read line
^-- SC1073: Couldn't parse this while loop.
In while-passwd.sh line 13:
do
^-- SC1061: Couldn't find 'done' for this 'do'.
In while-passwd.sh line 22:
^-- SC1062: Expected 'done' matching previously mentioned 'do'.
^-- SC1072: Unexpected eof. Fix any mentioned problems and try again.
Shellcheck finden Sie bei den gängigen Distributionen in Form eines gleichnamigen Pakets in den Repositories, sodass es sich über das Software-Management einspielen lässt. Fehlt das Tool in den Paketquellen der von Ihnen verwendeten Distribution, greifen Sie zum Quellcode von der Github-Seite des Projekts [1]. Alternativ bietet Shellcheck seine Dienste auch in Form eines Formulars auf der Projektwebseite an [2]. Abbildung 3 zeigt, wie das Testen von Shell-Code über das Online-Formular aussieht.
Die Fähigkeiten von Shellcheck beschränken sich jedoch nicht auf das bisher gezeigte Ausgabeformat: Über den Schalter -f (Langform --format) wechseln Sie zwischen Ausgabevarianten als XML (checkstyle) für den GCC, als JSON (json) und für das Terminal (tty) hin und her. Listing 2 und Abbildung 3 zeigen die Ausgabe für das Terminal, Listing 3 die Ausgabe als XML im Checkstyle-Format. Checkstyle ist ein Programm zur Analyse von statischem Quellcode [3].
Listing 3
$ shellcheck -f checkstyle cmdshow1.sh
<?xml version='1.0' encoding='UTF-8'?>
<checkstyle version='4.3'>
<file name='cmdshow1.sh' >
<error line='9' column='1' severity='error' message='Couldn't parse this while loop.' source='ShellCheck.SC1073' />
<error line='10' column='1' severity='error' message='Couldn't find 'done' for this 'do'.' source='ShellCheck.SC1061' />
<error line='18' column='1' severity='error' message='Expected 'done' matching previously mentioned 'do'.' source='ShellCheck.SC1062' />
<error line='18' column='1' severity='error' message='Unexpected eof. Fix any mentioned problems and try again.' source='ShellCheck.SC1072' />
</file>
</checkstyle>
Über den Schalter -s (--shell) bestimmen Sie den Shell-Dialekt, auf den Sie Shellcheck ansetzen. Das Programm unterstützt die Syntax der Bash, der Korn-Shell, die einer POSIX-kompatiblen Shell (Sh) sowie die Syntax der Zsh [4]. Geben Sie beim Aufruf keinen Dialekt an, leitet Shellcheck diesen aus der ersten Zeile des Skripts ab, dem sogenannten Shebang. Für die Bash lautet er beispielsweise #!/bin/bash.
Dash und Zsh
Unter Debian GNU/Linux fungiert derzeit die auf das Ausführen von Skripten getrimmte Dash [5] als Standard-Shell. Die Zsh müssen Sie selbst nachinstallieren – sie kommt zum Beispiel bei der österreichischen Linux-Distribution Grml [6] als Standard-Shell zum Einsatz (und war das auch eine Zeit lang bei Mac OS X). Beide Shells verstehen die zwei nützlichen Schalter -e (--errorexit) und -n (kurz für --noexec).
Mittels -e brechen beide Shells das Ausführen des Skripts ab, sobald ein ungetestetes Kommando Fehler verursacht. Das betrifft insbesondere Kontrollstrukturen wie if, elif, while und until sowie die linken Operanden von logischen Und- sowie Oder-Verknüpfungen (&& beziehungsweise ||). Über -n lesen Dash und Zsh ein Kommando, führen es aber genau wie die Bash nicht aus, sondern prüfen es lediglich auf syntaktische Korrektheit.
Externe Style-Checker
Oft erweisen sich auch externe Erweiterungen für den Texteditor oder die Entwicklungsumgebung als praktisch. Für Vim gibt es beispielsweise das Plugin Bash Support [7], das beim Speichern eines Shell-Skripts automatisch eine Syntaxprüfung vornimmt. Dazu ruft es im Hintergrund die Shell mit dem Schalter -n auf [8].
Ein weiteres Vim-Plugin, das auch für andere Skript- und Programmiersprachen zur Verfügung steht, ist Syntastic [9]. Sie finden es nicht nur auf Github, sondern auch in den Paketquellen vieler Distributionen. Abbildung 4 zeigt das Ergebnis einer Prüfung mithilfe des zusätzlichen Vim-Kommandos :Errors.
Für Emacs stehen der Font-Lock-Mode [10] zum Syntax-Highlighting sowie das Plugin Flycheck [11] bereit, das im Hintergrund auf Shellcheck zurückgreift [12]. Arbeiten Sie mit Eclipse, sollten Sie einen Blick auf BashEclipse [13] und das Bash-Editor-Plugin [14] werfen.
Für die Kommandozeile gibt es außerdem noch den Bash-Debugger BashDB [15]. Allerdings erfolgte dessen letzte Aktualisierung im Jahr 2011, weshalb wir das Tool hier nur der Vollständigkeit halber erwähnen und es von der weiteren Betrachtung ausnehmen. Das Analogon ZshDB [16] für die Zsh sieht hingegen deutlich frischer aus.
Ein Skript testen
Nachdem Sie die Schreibweise des Skripts auf Korrektheit überprüft haben und sicher wissen, dass die Syntax stimmt, steht ein Praxistest als notwendiger Zwischenschritt an, bevor Sie Ihr Skript in die freie Wildbahn entlassen. Das klappt am besten in einer Testumgebung, wie etwa in einem Chroot-Environment, einer Installation in der Virtualbox oder in einem Container.
Zum Test stellen Sie möglichst alle erlaubten Eingaben und die zu erwartenden Ausgaben zusammen. Das kann wiederum in Form eines Shell-Skripts erfolgen, das Ihr Skript mit allen Parameterkombinationen aufruft und prüft, ob daraus die gewünschten Rückgabewerte resultieren. Den Rückgabewert eines Linux-Kommandos erhalten Sie über das Kommando echo $? auf der Shell.
Zusätzliche Shell-Optionen
Als Nächstes aktivieren Sie über den Schalter -v (--verbose) den sogenannten Verbose-Modus der Shell. Damit sehen Sie bei der Ausführung des Skripts, welche Kommandos die Bash gerade aus dem Skript liest und verarbeitet. Sie sehen dabei die einzelnen Zeilen genauso, wie sie im Skript stehen, und darunter die dazugehörige Ausgabe. Damit betreiben Sie Ursachenforschung und wissen, welche Ausgabe woher kommt (Listing 4).
Listing 4
$ bash -v while-passwd.sh
#!/bin/bash
[...]
datafile="/etc/passwd"
FS=":"
while read line; do
# store field 1: username
F1=$(echo $line|cut -d$FS -f1)
# store field 2: home directory
F2=$(echo $line|cut -d$FS -f6)
# store field 3: login shell
F3=$(echo $line|cut -d$FS -f7)
echo "User \"$F1\" home directory is $F2 and login shell is $F3"
done < $datafile
echo $line|cut -d$FS -f1
echo $line|cut -d$FS -f6
echo $line|cut -d$FS -f7
User "root" home directory is /root and login shell is /bin/bash
[...]
Jetzt wäre es noch gut zu wissen, wie die Bash denn die einzelnen Kommandos tatsächlich ausführt, also welche Variablen sie beispielsweise setzt. Dazu rufen Sie die Bash entweder mit dem Schalter -x auf oder fügen am Anfang des Skripts das Kommando set -x hinzu.
Damit steigen Sie tiefer ins Debugging ein und finden zum Beispiel heraus, was die Shell tatsächlich verarbeitet und warum ein Kommando nicht wie erwartet funktioniert. Die Einzelschritte in Zeile 5 und 6 aus Listing 5 entsprechen der Zeile 9 aus Listing 4, das Ergebnis – die endgültige Zuweisung für die Variable F1 – finden Sie in Zeile 7.
In jeder Ausgabezeile von Listing 5 stehen am Anfang ein oder mehrere Pluszeichen. Deren Anzahl signalisiert die Hierarchieebene eines ausgeführten Kommandos. Beispielsweise steht vor dem Schleifenkopf nur ein Plus, aber vor jedem Teilkommando tauchen zwei Pluszeichen auf, um zu signalisieren, dass sie eine Hierarchieebene tiefer liegen. Sie ersehen daraus, wann ein Teilkommando etwas Falsches zurückliefert, und können die Fehlerstelle gezielt korrigieren.
Listing 5
$ bash -x while-passwd.sh
+ datafile=/etc/passwd
+ FS=:
+ read line
++ echo root:x:0:0:root:/root:/bin/bash
++ cut -d: -f1
+ F1=root
++ echo root:x:0:0:root:/root:/bin/bash
++ cut -d: -f6
+ F2=/root
++ echo root:x:0:0:root:/root:/bin/bash
++ cut -d: -f7
+ F3=/bin/bash
+ echo 'User "root" home directory is /root and login shell is /bin/bash'
User "root" home directory is /root and login shell is /bin/bash
[...]
Fazit
Oft fällt es nicht leicht, einen Fehler in einem Shell-Skript zu identifizieren. Mit den hier vorgestellten Methoden und Werkzeugen kommen Sie sicher ans Ziel. Trotzdem: Trockenübungen helfen nicht, nur Lesen, Lernen und das Ausprobieren in der täglichen Praxis bringt Sie wirklich weiter. Gute Startpunkte für zusätzliches Know-how geben in äußerst kompakter Form etwa der Advanced Bash Scripting Guide (ABS) [17] und das Bash Scripting Cheatsheet [18] ab.
Über den Autor
Der digitale Nomade Frank Hofmann arbeitet bevorzugt von Berlin, Genf und Kapstadt aus als Entwickler, Trainer und Autor. Er ist zudem Koautor des Debian-Paketmanagement-Buches (https://www.dpmb.org).
Infos
-
Shellcheck: https://github.com/koalaman/shellcheck
-
Shellcheck online: https://www.shellcheck.net
-
Checkstyle: http://checkstyle.sourceforge.net/
-
Zsh: https://www.zsh.org
-
Grml: http://grml.org
-
Bash-Support für Vim: https://github.com/vim-scripts/bash-support.vim
-
Vim als Bash-IDE: https://www.tecmint.com/use-vim-as-bash-ide-using-bash-support-in-linux
-
Syntastic: https://github.com/vim-syntastic/syntastic
-
Font-Lock-Mode für Emacs: https://www.gnu.org/software/emacs/manual/html_node/emacs/Font-Lock.html
-
Flycheck: https://www.flycheck.org
-
Bash-Syntax-Check in Emacs: http://skybert.net/emacs/bash-syntax-checking-in-emacs
-
BashEclipse: https://sourceforge.net/projects/basheclipse/
-
Bash-Plugin für Eclipse: https://github.com/de-jcup/eclipse-bash-editor
-
BashDB: http://bashdb.sourceforge.net
-
Advanced Bash Scripting Guide: http://www.tldp.org/LDP/abs/html
-
Bash Scripting Cheatsheet: https://devhints.io/bash








