Manchmal sind es die einfachsten Dinge, die man in seiner Betriebsblindheit übersieht, die aber zu den skurrilsten Fehlern führen. An denen hat man dann in aller Regel recht lange seinen Spaß. So wie wir…
Kurze Vorgeschichte, um das folgende nachvollziehen zu können (Stilistisch aufgepeppt, damit’s nicht langweilig wird):
Es war einmal eine Job-Monitoring-Anwendung, die während Rechenjobs auf weltweiten Computing Grids laufen diese überwacht und Informationen in fast-Echtzeit zum User überträgt. Informationen wie „Was macht der Job gerade?“, „Wieviele Ressourcen verbraucht er, und wieviele sind noch frei?“ oder „Mit was für einem Fehler ist der Job soeben abgeraucht?“. Sachen die der User eben braucht, um ggf. fehlerhafte Jobs zu debuggen. Soweit, so prächtig. Dieses Monitoring-Tool kann bisher Bash- und Pythonscripte überwachen (Neumittelhochdeutsch „supervisen“…). Um Bashscripte zu überwachen, wird eine modifizierte Bash eingesetzt. Und damit fingen die Probleme an…
(Obacht, es folgt ein epic post mit techie-talk drin. Es geht also recht weit ins Eingemachte…)
Die modifizierte Bash (Nennen wir sie mal foosh, die Foo Shell) muss, da im Grid beides vorkommt, sowohl für 32bit als auch für 64bit kompiliert werden. Diese Aufgabe übernahm der Kollege, der die foosh-Modifikationen vor einiger Zeit vornahm (Sie basiert auf der bash 3.2). Seitdem verrichtete sie brav ihre Dienste (Kommandos vor der Ausführung inspizieren und gegebenenfalls Monitoring-Daten über sie verschicken). Nun befindet sich das Monitoring-Tool in aktiver Entwicklung (u.a. durch mich). In diesem Rahmen wurde auch die foosh erneut angefasst und einige Änderungen vorgenommen. Foosh neu kompiliert, verpackt, getestet: Wunderbar.
Komplett wunderbar? Nein, es stellte sich heraus, dass foosh mit einigen Scripten Probleme hatte. Diese äußerten sich in stderr-Meldungen folgender Art:
bash: unexpected EOF while looking for matching ``'
bash: syntax error: unexpected end of file
Das Script konnte offensichtlich nicht korrekt geparsed werden. Der Ausschnitt des Scriptes, in dem der Fehler geschah, war folgender (Zeile des Scriptfehlers in rot):
newpath=""
for p in `echo ${CLASSPATH} | sed 's/:/ /g'`; do
if test ! "`echo ${p} | egrep CMT`" ; then
if test "${newpath}" = "" ; then
newpath=${p}
else
newpath=${newpath}:${p}
fi
fi
done
Offensichtlich wurde der Abschließende Backtick nach „CMT“ nicht gefunden. Die unmodifizierte bash machte hingegen mit dem Script keine Probleme. Auch die foosh in ihrer ursprünglichen Form (Ohne meine Änderungen, aber mit den Änderungen gegenüber der originalen bash 3.2, die vom Kollegen „damals“ eingebaut wurden) fraß das Script ohne zu zicken. Was macht also ein braver Programmierer? Richtig, er schaut sich seine Änderungen nochmal genau an, und/oder nimmt sie schrittweise zurück, um das Problem einzugrenzen.
Ich kürze den Vorgang, der aus Stunden angefüllt mit Kaffee (Oder bei mir eher Tee), Schweiß und Blut bestand, im Sinne des Lesers etwas ab – das Resultat jedoch war: Auch die völlig unveränderte foosh, von mir kompiliert, wies den Fehler auf. Die foosh, von meinem Kollegen damals kompiliert, nicht. Ich ging gar noch einen Schritt weiter, und kompilierte die original bash 3.2 – auch hier trat der Fehler auf. An dieser Stelle vermuteten wir einen Unterschied im Build-System. Also auf dem Rechner, auf dem ich die bashs kompilierte, musste irgendetwas anders sein, als auf der Kiste, auf der der Kollege damals die bashs gebaut hatte. Ich kontaktierte ihn, riss ihn aus seinem vertrauten Alltag (Okay, wir wollen’s nicht übertreiben…) und glich meine Buildskripte und Umgebung mit ihm ab.
Doch leider war es zwecklos. Der Fehler beharrte auf seiner Anwesenheit. Witzigerweise funktionierte das oben zitierte Stück code, für sich genommen, auch mit der von mir kompilierten foosh. Nur im Grid-Kontext trat der Fehler auf. Mittlerweile war die Zahl derer, die sich mit dem Problem befassten, auf (mit mir) drei angewachsen.
Zu dritt ermittelten wir nach angeregter Diskussion (Man nennt das ja immer so), dass der eigentliche Fehler nicht in dem zitierten Stück Code auftrat, sondern weiter oben (Oh wunder, oh wunder!) im Script, und zwar in folgender Zeile:
CMTBIN=`uname`-`uname -m | sed -e 's# ##g'`; export CMTBIN
Auch hier hantierte das Script mit Backticks, und hier wurde scheinbar der abschließende Backtick nicht gefunden. Die Zeile kam uns dennoch etwas komisch vor. Vor allem der sed-Aufruf. Hashmarks („Rauten“) leiten in Bashscripts Kommentare ein – könnte also eine der Hashmarks im sed-Aufruf als Kommentarbeginn misinterpretiert worden sein, so dass der abschließende Backtick nach ##g’ überlesen wurde? Und warum taten dies nur auf meinem System kompilierte Bashs (egal ob foosh oder vanilla)? Wir erstellten einen kleinen Testcase:
echo `seq -w 00 05 | sed -e 's# ##g'`
Und testeten weiter. Stocherten, zugegeben, ein wenig herum. Bis ich auf die banale Lösung kam…
Es folgt: Die LÖSUNG. Und vorsicht: Sie ist banal. So banal, dass ich sie eigentlich gar nicht schreiben mag. Aber da wir uns auf bitschupser befinden und nicht immer nur die anderen dämlich sind, sondern auch man selbst, sei hier die Lösung nicht vorenthalten.
Für die bash 3.2 wurden nach dem Release auch weiter Patche veröffentlicht. Der allerallererste davon, namens bash32-001 (ftp://ftp.gnu.org/gnu/bash/bash-3.2-patches/bash32-001), ist mit folgendem Kommentar des Patchautors versehen:
When using historical ``-style command substitution, bash incorrectly attempts
to interpret shell comments while scanning for the closing backquote.
…der Kollege, der die foosh damals kompilierte, hat sie wohl vor dem Kompilieren gepatcht. Was an und für sich ja die natürlichste Sache der Welt ist. Allerdings tat er dies, nachdem er die foosh-Sourcen in unser Code-Repository eincheckte. Ich checkte den Code aus, kompilierte ihn im Vertrauen, identischen Code zu benutzen, und so war das Problem geboren.
Seufz…
- Tim