Mailinglist Archive: opensuse-de (4684 mails)
| < Previous | Next > |
Re: bash: mehrface for-Schleife
- From: B.Brodesser@xxxxxxxxxxx (Bernd Brodesser)
- Date: Wed, 7 May 2003 10:04:36 +0200
- Message-id: <20030507080436.GC995@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx>
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:
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Ja. Und die lernt man kennen. ;)
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.
Aha, Du kannst C, das ist schon mal gut. Viele können es nicht.
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.
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.
Ja, da bin ich anfangs auch drüber gestolpert, da ich von UNIX her
das -s gewohnt bin. Bei GNU ist es wohl anders.
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
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
| < Previous | Next > |