Bernd Brodesser schrieb:
- Thomas Michalka schrieb am 02.Mai.2003:
Ich möchte vorab nur bemerken, daß _mein_ Skript ganz ausgezeichnet funktioniert.
VORSICHT!
Ich habe den thread nicht verfolgt, kann also durchaus sein, daß Dein Skript funktioniert, aber das kann man nicht durch einfaches probieren herausfinden. Es muß sichergestellt werden, daß Dein Skript immer so funktioniert, wie es soll, nicht nur manchmal. Das immer kann man aber nicht durch probieren testen.
Es ist zwar so, daß dieses Skript nur für eine ganz bestimmte Situation geschrieben wurde, aber trotzdem würde mich brennend interessieren, wie man bash-Skripte *professionell* testet (grundsätzliche Konzepte, spezielle Methoden). Gibt es in der bash vielleicht so eine Art dry-run-Option? Das wäre zwar auch kein "richtiger" Test, aber besser als nichts. (Es gibt ja z.B. auch 'set -x', aber ob das der richtige Einstieg ist?) Oder gibt es (für längere bash-Skripte ) sowas wie einen Debugger? Von Python z.B. weiß ich, daß es einen gibt, und - wenn man's unbedingt braucht - auch Entwicklungsumgebungen mit einigem Komfort.
Häufig gemachte Fehler:
Ein Skript funktioniert zwar immer, wenn es soll, aber leider auch manchmal, wenn es nicht soll.
Ein Skript verhält sich im Feherfall, merkwürdig.
Ein Skript bekommt Schwierigkeiten bei Dateinamen, die ein Leerzeichen oder gar ein Zeilenumbruch im Namen haben.
Wohlgemerkt, ich weiß nicht, ob es bei Deinem Skript der Fall ist.
In meinem Skript arbeitet nur rsync mit Dateien direkt, und schreibt nur auf Zieldateisystemen, die auf einer anderen Platte liegen. Da dort zunächst keine Dateien vorhanden sind, kann man das schon als eine Art Testumgebung sehen. Außerdem habe ich rsync vorher interaktiv anhand einzelner Dateisysteme ausprobiert, um die Wirkungsweise zu verstehen.
Man könnte auch eine Konfigurationsdatei aufbauen, die den gleichen Aufbau wie der echo haben kann, und dann geht es einfach mit:
cat datei | while read devnum mpoint source fsname; do ...
Schaut auch recht schön aus. Für mich war es nur alles andere als selbstverständlich, daß read einmal von stdin und von einer Zeichenkette so liest, daß nach jedem Whitespace quasi neu begonnen wird mit dem Lesevorgang.
Nicht nach jedem Whitespace, sondern nach jedem Zeilenumbruch.
Hier habe ich mich unpräzise ausgedrückt. Für mich war es nicht selbstverständlich, daß die bash z.B. whitespaces als Worttrenner betrachtet und daß sie die Wörter nun automatisch jeder Variablen nach read zuordnet (s. Abschnitt zu read in 'man bash').
Aber dann hast Du noch was ganz entscheidendes der bash noch nicht verstanden. read ließt von stdin, immer.
Das war es, was ich nicht wußte. Ich will echt nicht arrogant klingen, aber ich bestehe darauf, daß es einen Unterschied gibt zwischen "verstehen" und "Kenntnis haben" eines/von einem Sachverhalt. Wenn es nunmal so ist, daß read immer von stdin liest, dan muß man das einfach wissen, aber groß zu verstehen gibt es daran nichts. Um nochmal zu untermauern, daß ich schon ein gewisses Verständnis von der bash und dem Konzept von stdin/stdout habe, möchte ich den folgenden grundlegenden Unterschied aufzeigen: tom@tomcat> less filix bedeutet, daß less die Datei filix liest (und anzeigt). Generell liest less von Dateien auch von stdin. Deshalb funktioniert ja genauso tom@tomcat> cat filix | less Wenn nun z.B. tom@tomcat> rpm -qp *.rpm funktioniert, dann heißt das nicht (es könnte so sein), daß tom@tomcat> ls *.rpm | rpm -qp so funktioniert, wie man vordergründig meinen könnte, weil rpm eben *nicht* von einer Datei liest (egal, ob nun einfachen Text, oder ein anderes Format) sondern den Paketnamen als Argument betrachtet (Rpm gibt folgendes aus: "rpm: Es wurden keine Argumente für die Anfrage angegeben"). Deshalb muß man schreiben tom@tomcat> rpm -qp `ls *.rpm` Also die Ausgabe von ls *.rpm wird als Argument an rpm sozusagen übergeben. (Natürlich würde man letzteres nicht schreiben, weil die bash Zeichen wie '*' expandiert, so daß man mit *.rpm schon einen Argumentliste für rpm -qp bekommt. Ich habe schon mal einen *Useless use of ls award* eingehandelt - das reicht ;-) )
Nur wurde hier stdin durch den | umgelenkt. Die Standardeingabe des Befehls nach dem | ist die Standardausgabe des Befehls vor dem | Jeder Befehl, der nach dem | steht und von stdin ließt, ließt an dieser Stelle die Standardausgabe des Befehls vor dem | Wenn Du ein Skript schreibst, und jemand lenkt die Standardeingabe um, sei es durch < oder durch einen | so ließt Dein Skript nicht mehr vom Bildschirm, sondern aus einer Datei oder aus einem anderen Befehl. Dafür brauchst Du Dich in Deinem Skript nicht anzustrengen, daß macht die shell für Dich.
Dennoch muß ich dafür sorgen, daß mein Skript einen Datenstrom aus einer Datei (welcher Art auch immer) lesen kann.
Allerdings gibt es eine Möglichkeit dies zu umgehen, wenn man nicht von der Standardeingabe ließt, sondern von /dev/tty, so wird immer von der kontrollierende Konsole gelesen. passwd macht das bei Paßworteingabe.
Der Sinn der Sache ist hier ja, daß sichergestellt wird, daß das Paßwort auch auf der Konsole eingegeben wird, von wo der Login begehrt wurde.
Aber wehe, wenn man dann kein kontrollierendes Terminal hat. Dies ist bei Dämonen der Fall, oder bei Einträgen in der crontab oder in ip-up
Das "wehe" verstehe ich nicht. Wenn man keines hat, und das Skript versucht dennoch davon zu lesen, dann geht es halt nicht. Oder kann es hier ein subtileres Problem geben?
David Haller. Neben Jan derjenige, der immer um korrekte Skripts bemüht ist. An dieser Stelle mal einen dicken Dank an den beiden. Ich finde es enorm wichtig, daß der geneigte Newbie frühzeitig lernt korrekte Skripts zu schreiben und nicht irgendwelche irgendwie funktionierende. Warum habe ich schon weiter oben aufgeführt.
Ehrlich gesagt, bin ich auch eher einer, der es gern genauer nimmt, wenn - ja wennnnnnn - ich die Zeit dazu habe. Insofern war mein Skript für die Dateisystem-Sychronisation quick & dirty. Das war mir schon bewußt.
Natürlich schreibe ich auch selber schon mal schlampige Skripts für den Moment. Aber ich bin mir dessen bewußt. Ich weiß, daß das Skript nicht für Dateinamen mit Umbrüchen drin funktioniert, aber wenn im Verzeichnis auf dem ich es loslasse, keine solche Dateien gibt, ist es ja gut. Vorrausgesetzt ich lösche das Skript anschließend wieder. Will ich ein solches Skript behalten, so muß ich mir manigfaltige Gedanken machen.
Wenn ich es behalte, schreibe ich mir meistens eine dicke Warnung rein, bzw. echo diese schon am Anfang und frage ab, ob der Aufrufer das wirklich laufen lassen will.
Was ist, wenn ich einen Dateiname durch * abkürze und die shell ersetzt das nicht mit einem sondern mit zwei oder mehere Namen? Funktioniert mein Skript auch dann anständig. Was ist, wenn meine I-Nodes ausgegangen sind? Wie soll ich auf ein CTRL-C reagieren? Einfach beenden lassen, oder nicht doch lieber ein paar Aufräumarbeiten machen. Was ist, wenn der aufrufende User den Pfad total verändert hat, oder ...
Ja, leider kann man nie alle möglichen Fehlerquellen berücksichtigen, gottseidank aber die wahrscheinlichsten.
if ! mount | grep -q /dev/hdb$devnum; then
Du meine Güte, es geht wohl immer noch irgendwie einfacher? Aber ich habe nun mal nicht die Zeit, immer noch etwas päpstlicher als der Papst zu sein ;-)
Dahinter steckt aber wieder mal das allgemeine Unverständnis einer if-Entscheidung.
Jetzt verstehe ich wirklich nicht, was Du meinst. Du kannst doch hier wohl kein Unverständnis meinerseits meinen, oder?
Zwischen if und then, gehört ein beliebiger Befehl, besser gesagt Befehlsliste. Wenn diese den Exitcode 0 hat, so wird der then-Teil ausgeführt. Wenn diese Befehlsliste einen Exitcode ungleich 0 hat, so wird, falls vorhanden, der else-Teil ausgeführt.
Der häufigst gewählte Befehl hinter einem if ist test. [...] ist ein Synonym für test und nicht etwa ein Teil der bash-Syntax. Wenn Du [...] sagst, so rufst Du genauso einen Befehl auf, als ob Du mount oder grep sagst.
Jetzt ist mir klar, was Du meinst. Das ist mir schon klar, denn ... ... wenn man in C if ( Bedingung ) { ... } schreibt, sind die runden Klammern Bestandteil der C-Syntax, und der Code innerhalb der geschweiften Klammern wird ausgeführt, wenn die Bedingung [1] wahr ist. Im Gegensatz dazu sind die eckigen Klammern in der bash natürlich kein Syntaxelement, sondern ein Befehl, wenn auch "shell built-in". [1] "Bedingung" kann auch das Ergebnis eines Ausdrucks sein. Sie ist wahr, wenn das Ergebnis ungleich 0 ist. Das ist z.B. ein wichtiger Unterschied zur bash, wo die Bedingung wahr ist, wenn der Exitcode des zuletzt ausgeführten Kommandos/Prozesses gerade 0 ist, also der Erfolgsfall.
Wichtig ist zu verstehen, daß auch grep, wie jeder andere Befehl auch, einen Exitcode hat. Der ist 0 falls es eine Übereinstimmung gab, 1 falls es keinen gab und 2 falls es einen Syntaxfehler gab. Letzteres sollte in einem Skript nicht vorkommen. Und auf diesen Exitcode hin testet if
Ja, das verstehe ich schon. Aber das, was ich geschrieben hatte, nämlich if [ 0 -eq $(mount | grep -c "/dev/hdb$devnum") ] ; then mount /dev/hdb$devnum $MP_DIR/$mpoint ... fi arbeitet fast genauso, nur daß ich hier den Count-Output von grep nutze, und _teste_, ober dieser 0 ist. Das habe ich gemacht, weil ich unter 'man grep' (grep v2.4.2) über einen Silent-Modus zwar -s (silent) keine Ausgabe außer Fehlermeldungen gefunden habe, aber grep damit nicht arbeitet, wie beschrieben, sondern Fehlermeldungen werden gerade unterdrückt ('grep -c' gibt wenigstens _nur_ die Anzahl der Fundstellen - neben Fehlern - aus). Ich hätte noch ein tom@tomcat > grep --help | grep "-s" versuchen sollen. Dann hätte u.a ich gefunden -s, --no-messages Fehlermeldungen unterdrücken. -q, --quiet, --silent Alle normalen Ausgaben unterdrücken. wo '-s' und '-q' wohl richtig beschrieben sind.
Habe ich zuerst auch versucht, aber aus mir unerfindlichen Gründen bekam ich eine Meldung, wonach das Device schon gemountet sei, obwohl das *definitiv* nicht der Fall war. Das konnte ich reproduzieren, aber mir ist bis heute schleierhaft, was die Ursache ist. Leider habe ich gerade nicht genug Zeit, dem auf den Grund zu gehen.
Vielleicht war der Mountpoint schon gemoutet?
Wenn Du meinst, ob auf dem Mountpoint schon ein anderes Geräte gemountet war, kann ich auch sagen: definitiv nein. Aber macht auch nichts, das finde ich noch irgenwann raus. Allerdings ist es mit dem mount-Befehl sowieso keine ganz unkritische Sache. Manchmal kann es zu Timing-Problemen kommen, nämlich dann, wenn der entsprechende Eintrag in /etc/mtab nicht gleich gemacht wird. Dann wäre z.B. die Abfrage mit mount erfolglos, wenn man "zu früh" abfragt. Was nun "zu früh" oder "zu spät" ist, muß man erstmal verstehen. Vielleicht sollte man nach einem mount oder umount generell noch ein sleep einbauen und sich den Erfolg der Operation durch eine andere Abfrage sicherheitshalber bestätigen lassen. Gruß, Thomas