Hallo Thomas, Erst mal Sorry, für die späte Reaktion. Hatte die Mail zurückgestellt und dann doch glatt vergessen. :( * Thomas Michalka schrieb am 03.Mai.2003:
Bernd Brodesser schrieb:
* Thomas Michalka schrieb am 02.Mai.2003:
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?)
Muß zugeben, daß Du mich da ein wenig auf dem flsachen Fuß erwischst. Ich wüßte da nichts. Allerdings sind Skripte, im Gegensatz zu Programme, im Allgemeinen nicht so unüberschaubar groß, als daß sie nicht mehr einfach zu überblicken wären.
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.
Damit wollte ich andeuten, daß ich mir Dein Skript nicht angesehen habe, und auch nicht weiß, was es macht. Ich bin nur auf Deine Antwort auf Jan eingegangen, und was mir dazu einfiel.
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.
Ich will übrigens auch nicht arrogant klingen. Ist manchmal ganz schön schwierig das zu vermeiden. Das nicht verstanden haben, soll auf keinem Fall was abwertendes haben.
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.
Es ging weniger speziell um read, sondern um alle Befehle, die von der Standardeingabe lesen. Die Standardeingabe kann umgelenkt werden. Da kann der jeweilige Befehl nichts dran ändern. Es ist eine Eigenschaft der shell. Das der Befehl ein shellinterner ist, ist dabei nicht wichtig.
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
Dieses unterschiedliche Verhalten ist aber eine Eigenschaft von less. Wenn Du den Dateinamen angibst, dann ist es less, daß die Datei öffnet. Wenn kein Dateiname angegeben ist, so wird von stdin gelesen. So verhalten sich zwar viele Befehle, aber es liegt in der Gewalt des jeweiligen Befehls. Was anderes wäre less < filix jetzt bekommt less nicht mit, daß die Eingabe umgelenkt ist. Das merkt man daran, daß less keinen Dateinamen angibt. Woher auch? Allerdings ist less kein besonders gutes Beispiel. less merkt, wenn es vom Bildschirm liest und veweigert seinen Dienst mit einer Fehlermeldung. Gib mal nur less ein. Ein normaler Linuxbefehl merkt das nicht.
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").
Ja, Argument ist nicht die Standardeingabe. Was Du suchst ist xargs xargs macht aus der Standardeingabe Argumente. In der Einfachsten Form funktioniert xargs wie folgt: foo | xargs befehl feste Argumente Dabei sein foo irgend ein Befehl, der irgend welche Ausgaben hat. Durch die Pipe | wird diese Ausgabe zur Eingabe von xargs. Das macht die shell, da kann weder foo noch xargs was dran ändern. Genaugenommen sogar in Verbindung mit dem Kernel. Die shell sieht die Pipe und macht den Systemaufruf pipe. Siehe hierzu man 2 pipe xargs nun ist ein Befehl, der als erstes Argument einen weitern Befehl hat und als folgende Argumente weitere Argumente zu diesem Befehl. xargs macht nichts anderes als den Befehl befehl aufzurufen und zwar mit den festen Argumenten die nach befehl aufgeführt sind und danach die Standardeingabe als Argumente.
Deshalb muß man schreiben
tom@tomcat> rpm -qp `ls *.rpm`
Also die Ausgabe von ls *.rpm wird als Argument an rpm sozusagen übergeben.
Ja, das ist die andere Möglichkeit. Das ist einer der Ersetzungsmechanismen der shell. Sie ersetzt den Ausdruck `...` durch die Ausgabe dessen, was innerhalb der ` ` steht.
(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.
Ja, Was ich meine, Du brauchst Dich nicht darum zu kümmern, wie ein Befehl einmal aus einer Datei, einmal aus einer pipe und einmal von der Tastatur liest. Das macht schon die shell. Allerdings was anderes ist es, wenn man wie oft üblich nur die Datei angeben können will, und der Befehl liest dann daraus. Das ist wie gesagt was anders, wenn man das will, muß man sich schon darum kümmern, ansonsten muß der Anwender, wenn er aus einer Datei lesen will, die Eingabe umlenken.
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.
Ja. Ich wollte nur andeuten, daß es geht. Aber dazu muß der Befehl Aufwand betereiben. Wenn der Befehl sich Gleich verhält, egal ob er von der Tastatur horscht oder aus einer pipe oder aus einer Datei, dann braucht kein Aufwand getrieben zu werden.
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.
Dann ist ja gut. Wirklich. quick & dirty machen wir alle mal. Aber daß muß einem schon bewußt sein, und es ist immer gut, zu versuchen es richtig zu machen, um Übung zu bekommen.
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.
Ja. Und die lernt man kennen. ;)
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?
Ich meinte, daß viele nicht verstehen, wie if funktioniert. Man sieht sehr häufig sowas wie: foo if [ $? = 0 ] then ... fi dabei wäre if foo then ... fi viel einfacher.
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
Aha, Du kannst C, das ist schon mal gut. Viele können es nicht.
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".
Ja. Es gibt diesen Befehl übrigens auch extern. /usr/bin/[ ist ein link auf /usr/bin/test und verhält sich wie der 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.
Ja, so ist es. Das hat den einfachen Grund, daß man bei der shell noch unterscheiden kann, warum was schief gegangen ist, durch unterschiedliche Exitcodes, wärend geklappt ist geklappt.
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
Ja, da bin ich anfangs auch drüber gestolpert, da ich von UNIX her das -s gewohnt bin. Bei GNU ist es wohl anders.
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.
Bernd -- ROTFL = Rolling On The Floor, Laughing = Auf dem Boden wälzen, lachend. SCNR = Sorry, Could Not Resist = Sorry, Ich konte nicht widerstehen. AFAIK = As Far As I Know = So weit ich weis|BTW = By The Way = Nebenbei bemerkt IMHO = In My Humble Opinion = meiner bescheidenen Meinung nach |Zufallssig. 9