Sorry, mein sendmail hat rumgesponnen, deshalb forwarde ich mal
meine eigne Mail.
----- Forwarded message from "Jan Trippler\""
Manfred Tremmel schrieb:
Sorry, 2. Versuch, die erste Mail war noch nicht fertig ...
Am Samstag, 26. April 2003 13:59 schrieb Thomas Michalka:
[...] Also wenn die Werte zusammengehören und Du die auch in eine Variable legen kannst, tuts vielleicht ein:
VAR="A|0|Z B|1|Y C|2|X D|3|W E|4|V F|5|U"
for param in $VAR ; do parm_v=`echo $param | awk -F \| '{print $1}'` parm_n=`echo $param | awk -F \| '{print $2}'` parm_t=`echo $param | awk -F \| '{print $3}'` echo $param_v $param_n $param_t done
Es tut! Die Werte gehören in der Tat zusammen. Deswegen ist Deine Idee echt elegant und funktioniert.
Ich würde dafür nur keinen awk nehmen, ein einfacher cut -f1 -d\| tuts auch. awk ist dafür deutlich oversized und auch etwas ressourcenfressender. jan@k500:~/tmp> ls -l /bin/gawk /usr/bin/cut -rwxr-xr-x 1 root root 209420 Mär 25 2002 /bin/gawk -rwxr-xr-x 1 root root 18968 Mär 23 2002 /usr/bin/cut
awk '{print $'$position'}' # hier darf außer zw. 'print' und '$' KEIN Leerraum enthalten sein!!!
Unsinn, wo hast Du das denn her? jan@k500:~/tmp> pos=2 jan@k500:~/tmp> echo "1 2 3" | awk ' { print $'$pos' } ' 2 Lediglich das Konstrukt $'$pos' muss zusammen geschrieben werden. BTW: jan@k500:~/tmp> echo "1 2 3" | cut -f$pos -d" " 2 funktioniert genauso gut. [...]
# The mount point directory MP_DIR=/mnt
# ------------------------------------------------------------------------ POS_NUM=" 1 2 3 4 5 6 7 8 9" # ------------------------------------------------------------------------ DEV_NUM=" 2 5 6 7 8 9 10 11 12" MPT_LST=" 02 05 06 07 08 09 10 11 12" SRC_LST=" /boot / /root /tmp /opt /usr /usr/local /var /home" NME_LST=" BOOT_FS ROOT_FS ADMIN_FS TMP_FS OPT_FS USR_FS USRLOCAL_FS VAR_FS HOME_FS"
# Let's sync the filesystems now! for position in $POS_NUM ; do
devnum=$(echo -n $DEV_NUM | awk '{print $'$position'}') mpoint=$(echo -n $MPT_LST | awk '{print $'$position'}') source=$(echo -n $SRC_LST | awk '{print $'$position'}') fsname=$(echo -n $NME_LST | awk '{print $'$position'}')
Finde ich ziemlich umständlich, jetzt wo ich sehe, was Du damit machen willst. Wie wäre es so: echo "2 02 /boot BOOT_FS 5 05 / ROOT_FS 6 06 /tmp TMP_FS" |\ while read devnum mpoint source fsname; do ... done 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 ...
# Is the file system already mounted? if [ 0 -eq $(mount | grep -c "\/dev\/hdb$devnum") ] ; then
David würden sich jetzt wieder die Fußnägel aufrollen ;-) if ! mount | grep -q /dev/hdb$devnum; then
mount /dev/hdb$devnum $MP_DIR/$mpoint & PID_MOUNT=$! wait $PID_MOUNT 2> /dev/null
Was hat das für einen Sinn? Du schickst den Prozess in den Hintergrund und anschließend wartest Du doch auf ihn. Warum dann nicht gleich mount /dev/hdb$devnum $MP_DIR/$mpoint und ich würde den Status abfragen! if ! mount /dev/hdb$devnum $MP_DIR/$mpoint; then echo Fehler beim mount exit 1 fi
fi
# RSync doesn't need a trailing '/' when syncing the root file system if [ "$source" = "/" ] ; then source=""
source=
fi echo -en "$(date +'%T'): Copying files in $fsname from $source/ to $MP_DIR/$mpoint/ ... "
# The '/' after 'source' ist MOST important to not copy # the mount point directory itself but its contents (see 'man (1) rsync')! rsync $RSYNC_OPTS $source/ $MP_DIR/$mpoint/ & PID_RSYNC=$! wait $PID_RSYNC 2> /dev/null ; echo "done (process $PID_RSYNC)."
Auch hier wieder: Was hat es für einen Sinn, das Kommando in den Hintergrund zu schicken, wenn Du doch auf die Beendigung wartest?
umount /dev/hdb$devnum
done
Jan ----- End forwarded message -----
Hallo, danke für den Kommentar auf mein Skript! Hätte ich gar nicht erwartet ... Ich möchte vorab nur bemerken, daß _mein_ Skript ganz ausgezeichnet funktioniert. Deshalb nehme ich Deinen Kommentar mal einfach als Beitrag zur ästhetischen Verbesserung, d.h. um das Skript eleganter (= meistens einfacher) zu machen, und das kann ja durchaus ebenso lehrreich sein. Ich bin schon ganz gespannt, wie meine persönlichen Fortschritte im bash-scripting sind, denn eigentlich wollte ich es vermeiden und stattdessen lieber Python verwenden. Aber ich sehe schon jetzt, daß zumindest für kleine Angelegenheiten - wenn keine komplexen Abläufe oder umfangreiche Textverarbeitung gefragt sind - bash-Skripte fast unausweichlich sind. Jan Trippler schrieb:
Thomas, Deine Zeilen sind zu lang - die Lesbarkeit leidet deutlich!
Ja, das war mir klar, als ich vorübergehend den automatischen Zeilenumbruch auf 100 Zeichen erhöht habe. Der Grund war, daß sonst mein Skript fast unlesbar geworden wäre. Leider war ich auch zu faul, um manuell <Enter> zu drücken nach ungefähr 70 - 80 Zeichen. Das werde ich jetzt beherzigen. Aber bei Skripten habe ich, ehrlich gesagt, nichts gegen eine Zeilenlänge bis 100 Zeichen. In normalem Text habe ich auch 70 - 80 Zeichen *viel* lieber.
On Son, 27 Apr 2003 at 21:48 (+0200), Thomas Michalka wrote:
Manfred Tremmel schrieb:
Sorry, 2. Versuch, die erste Mail war noch nicht fertig ...
Am Samstag, 26. April 2003 13:59 schrieb Thomas Michalka:
[...] Also wenn die Werte zusammengehören und Du die auch in eine Variable legen kannst, tuts vielleicht ein:
VAR="A|0|Z B|1|Y C|2|X D|3|W E|4|V F|5|U"
for param in $VAR ; do parm_v=`echo $param | awk -F \| '{print $1}'` parm_n=`echo $param | awk -F \| '{print $2}'` parm_t=`echo $param | awk -F \| '{print $3}'` echo $param_v $param_n $param_t done
Ich würde dafür nur keinen awk nehmen, ein einfacher cut -f1 -d\|
Ganz gut, das habe ich anläßlich eines anderen Skripts erst danach herausgefunden.
tuts auch. awk ist dafür deutlich oversized und auch etwas ressourcenfressender. jan@k500:~/tmp> ls -l /bin/gawk /usr/bin/cut -rwxr-xr-x 1 root root 209420 Mär 25 2002 /bin/gawk -rwxr-xr-x 1 root root 18968 Mär 23 2002 /usr/bin/cut
Aber sei mal ehrlich, sollte mich das bei 1 GB Hauptspeicher wirklich jucken?
awk '{print $'$position'}' # hier darf außer zw. 'print' und '$' KEIN Leerraum enthalten sein!!!
Unsinn, wo hast Du das denn her? jan@k500:~/tmp> pos=2 jan@k500:~/tmp> echo "1 2 3" | awk ' { print $'$pos' } ' 2
Lediglich das Konstrukt $'$pos' muss zusammen geschrieben werden.
So ist es! Eigentlich wollte ich mich nur auf das Statement innnerhalb der Klammer beziehen, aber ich habe das nicht korrekt ausgedrückt.
BTW: jan@k500:~/tmp> echo "1 2 3" | cut -f$pos -d" " 2
funktioniert genauso gut.
Auch sehr schön!
[...]
# The mount point directory MP_DIR=/mnt
# ------------------------------------------------------------------------ POS_NUM=" 1 2 3 4 5 6 7 8 9" # ------------------------------------------------------------------------ DEV_NUM=" 2 5 6 7 8 9 10 11 12" MPT_LST=" 02 05 06 07 08 09 10 11 12" SRC_LST=" /boot / /root /tmp /opt /usr /usr/local /var /home" NME_LST=" BOOT_FS ROOT_FS ADMIN_FS TMP_FS OPT_FS USR_FS USRLOCAL_FS VAR_FS HOME_FS"
# Let's sync the filesystems now! for position in $POS_NUM ; do
devnum=$(echo -n $DEV_NUM | awk '{print $'$position'}') mpoint=$(echo -n $MPT_LST | awk '{print $'$position'}') source=$(echo -n $SRC_LST | awk '{print $'$position'}') fsname=$(echo -n $NME_LST | awk '{print $'$position'}')
Finde ich ziemlich umständlich, jetzt wo ich sehe, was Du damit machen willst. Wie wäre es so:
echo "2 02 /boot BOOT_FS 5 05 / ROOT_FS 6 06 /tmp TMP_FS" |\ while read devnum mpoint source fsname; do ... done
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. Ich habe mir 'man bash' natürlich auch angesehen, aber das Ding ist ja sooo lang, und nicht gerade didaktisch gegliedert (was wohl auch nicht beabsichtigt war), und da wußte ich als Bash-Unerfahrener noch nichts von 'read'. Überhaupt hält die bash wohl noch viel überraschendes für mich bereit ...
# Is the file system already mounted? if [ 0 -eq $(mount | grep -c "\/dev\/hdb$devnum") ] ; then
David würden sich jetzt wieder die Fußnägel aufrollen ;-)
Wen meinst Du? Ich habe von keinem David in diesem Thread gelesen, oder ist das so ein Insider-Witz?
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 ;-)
mount /dev/hdb$devnum $MP_DIR/$mpoint & PID_MOUNT=$! wait $PID_MOUNT 2> /dev/null
Was hat das für einen Sinn? Du schickst den Prozess in den Hintergrund und anschließend wartest Du doch auf ihn. Warum dann nicht gleich mount /dev/hdb$devnum $MP_DIR/$mpoint
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.
und ich würde den Status abfragen! if ! mount /dev/hdb$devnum $MP_DIR/$mpoint; then echo Fehler beim mount exit 1 fi
Ist natürlich viel sauberer. Aber wenn das Gerät schon gemountet ist, ergibt 'echo $?' -> 32. In dem Fall soll das Skript weitermachen. Deshalb ist Deine Lösung hier auch etwas zu einfach. (Keine Angst, damit komme ich klar :-) )
rsync $RSYNC_OPTS $source/ $MP_DIR/$mpoint/ & PID_RSYNC=$! wait $PID_RSYNC 2> /dev/null ; echo "done (process $PID_RSYNC)."
Auch hier wieder: Was hat es für einen Sinn, das Kommando in den Hintergrund zu schicken, wenn Du doch auf die Beendigung wartest?
Das hier hat den historischen Grund, daß nach dem Start von rsync noch andere Jobs parallel laufen sollten, aber sichergestellt werden mußte, daß der nächste rsync-Job _nach_ dem vorherigen gestartet würde. Die Teile zwischen PID_RSYNC=$! und dem wait-Kommando wurden gelöscht, aber der Rest sollte erhalten bleiben, falls es sich wieder einmal anders ergeben würde. Danke nochmals für die nützlichen Hinweise. Kannst Du mir vielleicht ein Buch über die Bash empfehlen? Ich habe da eher an ein Nachschlagewerk gedacht, als an ein klassisches Lehrbuch, was man von vorne bis hinten durcharbeiten muß. Ich brauche eigentlich auch keine Hinweise über das Programmieren als solches, eher schon weitergehende Tips über effizientes Scripting mit der bash. Und das wichtigste: Einen guten und ausführlichen Index sollte es haben. Gruß, Thomas
* 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. 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.
Deshalb nehme ich Deinen Kommentar mal einfach als Beitrag zur ästhetischen Verbesserung, d.h. um das Skript eleganter (= meistens einfacher) zu machen, und das kann ja durchaus ebenso lehrreich sein.
Es ist immer lehrreich sich auf Jan und David einzulassen.
Ich bin schon ganz gespannt, wie meine persönlichen Fortschritte im bash-scripting sind, denn eigentlich wollte ich es vermeiden und stattdessen lieber Python verwenden.
Um bash wirst Du nicht herum kommen. Aber das eine schließt das andere nicht aus.
Aber ich sehe schon jetzt, daß zumindest für kleine Angelegenheiten - wenn keine komplexen Abläufe oder umfangreiche Textverarbeitung gefragt sind - bash-Skripte fast unausweichlich sind.
So ist es. Ich kenne Python nicht, kann mir aber gut vorstellen, daß es für umfangreiche Textverarbeitung Sinnvoll ist. Dafür verwende ich perl. Für andere umfangreiche, besonders Zeitkritische Angelegenheiten ist C das geeignetere. Oder auch C++
Jan Trippler schrieb:
Thomas, Deine Zeilen sind zu lang - die Lesbarkeit leidet deutlich!
Ja, das war mir klar, als ich vorübergehend den automatischen Zeilenumbruch auf 100 Zeichen erhöht habe. Der Grund war, daß sonst mein Skript fast unlesbar geworden wäre. Leider war ich auch zu faul, um manuell <Enter> zu drücken nach ungefähr 70 - 80 Zeichen. Das werde ich jetzt beherzigen. Aber bei Skripten habe ich, ehrlich gesagt, nichts gegen eine Zeilenlänge bis 100 Zeichen. In normalem Text habe ich auch 70 - 80 Zeichen *viel* lieber.
Skripte kann man meist umbrechen, wenn das letzte, aber wirklich letzte Zeichen vor dem Zeilenumbruch ein \ ist. Insbesondere darf kein Leerzeichen mehr folgen. Natürlich darf sowas nicht mitten in einem Wort geschehen und auch nicht in irgend was gequotetem. Auf der Standardkonsole werden nur 80 Zeichen angezeigt.
tuts auch. awk ist dafür deutlich oversized und auch etwas ressourcenfressender. jan@k500:~/tmp> ls -l /bin/gawk /usr/bin/cut -rwxr-xr-x 1 root root 209420 Mär 25 2002 /bin/gawk -rwxr-xr-x 1 root root 18968 Mär 23 2002 /usr/bin/cut
Aber sei mal ehrlich, sollte mich das bei 1 GB Hauptspeicher wirklich jucken?
Bei einem einzelnen Skript, daß nur ab und an aufgerufen wird sicherlich nicht. Aber man sollte es sich zur Gewohnheit machen ressourcensparend zu programmieren. Spätestens wenn ein Teil in einer Schleife steckt, kann es eng werden.
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. Aber dann hast Du noch was ganz entscheidendes der bash noch nicht verstanden. read ließt von stdin, immer. 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. 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. 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 Wenn man /dev/tty verwendet, sollte man genau wissen, was man tut. Fast immer ist es richtig von der Standardeingabe zu lesen und dort hinein zu schreiben.
Ich habe mir 'man bash' natürlich auch angesehen, aber das Ding ist ja sooo lang, und nicht gerade didaktisch gegliedert (was wohl auch nicht beabsichtigt war), und da wußte ich als Bash-Unerfahrener noch nichts von 'read'.
Die manpages sind ganz allgemein nicht als Tutorial gedacht. Es ist ein Nachschlagewerk für Leute, die grundsetzlich den jeweiligen Befehl kennen. Ok, bei einfachen Befehlen kann man aus der man lernen, aber bei man bash sicherlich nicht.
Überhaupt hält die bash wohl noch viel überraschendes für mich bereit ...
Die bash ist immer für eine Überraschung gut.
# Is the file system already mounted? if [ 0 -eq $(mount | grep -c "\/dev\/hdb$devnum") ] ; then
David würden sich jetzt wieder die Fußnägel aufrollen ;-)
Ja. ;)
Wen meinst Du? Ich habe von keinem David in diesem Thread gelesen, oder ist das so ein Insider-Witz?
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. 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. 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 ...
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. 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. 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
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?
und ich würde den Status abfragen! if ! mount /dev/hdb$devnum $MP_DIR/$mpoint; then echo Fehler beim mount exit 1 fi
Ist natürlich viel sauberer. Aber wenn das Gerät schon gemountet ist, ergibt 'echo $?' -> 32. In dem Fall soll das Skript weitermachen. Deshalb ist Deine Lösung hier auch etwas zu einfach. (Keine Angst, damit komme ich klar :-) )
Das wäre dann einer der ganz seltenen Fälle, wo man $? tatsächlich braucht. Meist reicht eine if-Abfrage oder noch einfacher || bzw. &&
Danke nochmals für die nützlichen Hinweise. Kannst Du mir vielleicht ein Buch über die Bash empfehlen? Ich habe da eher an ein Nachschlagewerk gedacht, als an ein klassisches Lehrbuch, was man von vorne bis hinten durcharbeiten muß. Ich brauche eigentlich auch keine Hinweise über das Programmieren als solches, eher schon weitergehende Tips über effizientes Scripting mit der bash. Und das wichtigste: Einen guten und ausführlichen Index sollte es haben.
Ich kann Dir leider keins nennen, aber schau Dich doch mal in einer Bücherei um. Es ist sowieso besser, wenn Du ein wenig in einem Buch blätterst, bevor Du es kaufst. Es muß vielleicht nicht explizit bash sein. ksh tut es auch, bash ist mehr oder weniger eine Obermenge von ksh und das wiederum eine Obremenge von sh 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
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
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
Moin, On Fre, 02 Mai 2003 at 17:09 (+0200), Thomas Michalka wrote: [...]
Ich möchte vorab nur bemerken, daß _mein_ Skript ganz ausgezeichnet funktioniert. Deshalb nehme ich Deinen Kommentar mal einfach als
Sonst hätte ich schon danach gefragt ;-) [Zeilenlänge]
Aber bei Skripten habe ich, ehrlich gesagt, nichts gegen eine Zeilenlänge bis 100 Zeichen. In normalem Text habe ich auch 70 - 80 Zeichen *viel* lieber.
Hmm, auch innerhalb eines Scripts kann man (meist) mit 70 - 80 Zeichen gut leben, man kann die Zeilen ja per \ fortsetzen. Aber das ist mehr oder weniger Geschmacksache. [...]
jan@k500:~/tmp> ls -l /bin/gawk /usr/bin/cut -rwxr-xr-x 1 root root 209420 Mär 25 2002 /bin/gawk -rwxr-xr-x 1 root root 18968 Mär 23 2002 /usr/bin/cut
Aber sei mal ehrlich, sollte mich das bei 1 GB Hauptspeicher wirklich jucken?
Ja, das sollte eigentlich _immer_ eine Rolle spielen. Auch 1 GB RAM sind mal alle (Unix ist ein Multi-User-System, man sollte immer davon ausgehen, dass man nicht allein auf dem System arbeitet).
jan@k500:~/tmp> echo "1 2 3" | awk ' { print $'$pos' } ' 2
Lediglich das Konstrukt $'$pos' muss zusammen geschrieben werden.
So ist es! Eigentlich wollte ich mich nur auf das Statement innnerhalb der Klammer beziehen, aber ich habe das nicht korrekt ausgedrückt.
Das obige Konstrukt lebt ja davon, dass dem awk quasi ein { print $2 } *untergeschoben* wird. Man kann dem awk aber auch Shell-Variablen übergeben: jan@k500:~> echo pos1 pos2 pos3 | awk ' { print $pos } ' pos=2 pos2 [...]
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.
Genauer gesagt ist es die Variable $IFS, die bestimmt, welche Zeichen als Trenner genutzt werden. Das kann man durch Setzen von IFS steuern. [...]
if [ 0 -eq $(mount | grep -c "\/dev\/hdb$devnum") ] ; then
David würden sich jetzt wieder die Fußnägel aufrollen ;-)
Wen meinst Du? Ich habe von keinem David in diesem Thread gelesen, oder ist das so ein Insider-Witz?
Naja, ein wenig. David Haller schreibt hier sehr oft (im Moment ist er wohl ausser Haus ;-) - unter anderem zu Shell-Themen und bei dem obigen umständlichen Konstrukt hätte er mit Sicherheit rumgemosert.
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 ;-)
Man muss sich beim Bash-Scripting einfach immer wieder die Tatsache ins Gedächtnis rufen, dass die if-Abfrage einfach auf 0 / 1 reagiert. Auch der test (die [] sind auch nur ein Aufruf von test) liefert nur ein 0 oder != 0 zurück. Darum kann man beim Prüfen des Erfolgs eines Kommandos den test getrost weglassen, weil er in diesem Fall nur doppelt gemoppelt ist. [...]
und ich würde den Status abfragen! if ! mount /dev/hdb$devnum $MP_DIR/$mpoint; then echo Fehler beim mount exit 1 fi
Ist natürlich viel sauberer. Aber wenn das Gerät schon gemountet ist, ergibt 'echo $?' -> 32. In dem Fall soll das Skript weitermachen. Deshalb ist Deine Lösung hier auch etwas zu einfach. (Keine Angst, damit komme ich klar :-) )
Nein, wir befinden uns im Script hier im Zweig (siehe oben): if ! mount | grep -q /dev/hdb$devnum; then Der Fall *schon gemountet* kann an dieser Stelle also nicht eintreten. Welchen Sinn hätte sonst die if-Afrage?
rsync $RSYNC_OPTS $source/ $MP_DIR/$mpoint/ & PID_RSYNC=$! wait $PID_RSYNC 2> /dev/null ; echo "done (process $PID_RSYNC)."
Auch hier wieder: Was hat es für einen Sinn, das Kommando in den Hintergrund zu schicken, wenn Du doch auf die Beendigung wartest?
Das hier hat den historischen Grund, daß nach dem Start von rsync noch andere Jobs parallel laufen sollten, aber sichergestellt werden mußte, daß der nächste rsync-Job _nach_ dem vorherigen gestartet würde. Die Teile zwischen PID_RSYNC=$! und dem wait-Kommando wurden gelöscht, aber der Rest sollte erhalten bleiben, falls es sich wieder einmal anders ergeben würde.
OK, dann macht es Sinn. Ich halte es dann so, dass ich einen Kommentar an die betreffende Stelle schreibe (nach dem Motto: # hierher kommen alle parallel-Jobs) um selbst nach einem halben Jahr noch durchzusteigen, warum ich so und nicht anders gescriptet habe.
Danke nochmals für die nützlichen Hinweise. Kannst Du mir vielleicht ein Buch über die Bash empfehlen? Ich habe da eher an ein Nachschlagewerk gedacht, als an ein klassisches Lehrbuch, was man von vorne bis hinten durcharbeiten muß. Ich brauche eigentlich auch keine Hinweise über das Programmieren als solches, eher schon weitergehende Tips über effizientes Scripting mit der bash. Und das wichtigste: Einen guten und ausführlichen Index sollte es haben.
Im allgemeinen wird der Kofler (Michael Kofler: Linux - Installation, Konfiguration, Anwendung; Addison-Wesley; ISBN 3-8273-1475-5) als Einsteigerbuch empfohlen, der befasst sich aber nicht nur mit der bash. Stöbere mal bei O'Reilly rum, deren Titel sind eigentlich immer zu empfehlen. Ansonsten sind die im System vorhandenen Scripts eine gute Lektüre. Jan
Jan Trippler schrieb:
Moin,
On Fre, 02 Mai 2003 at 17:09 (+0200), Thomas Michalka wrote: [...]
jan@k500:~/tmp> ls -l /bin/gawk /usr/bin/cut -rwxr-xr-x 1 root root 209420 Mär 25 2002 /bin/gawk -rwxr-xr-x 1 root root 18968 Mär 23 2002 /usr/bin/cut
Aber sei mal ehrlich, sollte mich das bei 1 GB Hauptspeicher wirklich jucken?
Ja, das sollte eigentlich _immer_ eine Rolle spielen. Auch 1 GB RAM sind mal alle (Unix ist ein Multi-User-System, man sollte immer davon ausgehen, dass man nicht allein auf dem System arbeitet).
Ja, ok. Mir ist es auch schon mal passiert, daß ein amoklaufender Prozeß (kein Skript) das System lahmgelegt hat, denn nachdem er fast den ganzen Speicher belegt hat, fing das System an, ohne Ende zu swappen. Interaktion war dann passe.
jan@k500:~/tmp> echo "1 2 3" | awk ' { print $'$pos' } ' 2
Lediglich das Konstrukt $'$pos' muss zusammen geschrieben werden.
So ist es! Eigentlich wollte ich mich nur auf das Statement innnerhalb der Klammer beziehen, aber ich habe das nicht korrekt ausgedrückt.
Das obige Konstrukt lebt ja davon, dass dem awk quasi ein { print $2 } *untergeschoben* wird. Man kann dem awk aber auch Shell-Variablen übergeben:
jan@k500:~> echo pos1 pos2 pos3 | awk ' { print $pos } ' pos=2 pos2
Das ist doch keine Parameterübergabe im eigentlichen Sinn, also kein Argument für awk. Die Zuweisung pos=2 gehört doch zur Bash. *Bevor* awk letztlich aufgerufen wird, wird schon in der Shell 'pos' durch '2' ersetzt, oder sehe ich das falsch?
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.
Genauer gesagt ist es die Variable $IFS, die bestimmt, welche Zeichen als Trenner genutzt werden. Das kann man durch Setzen von IFS steuern.
Ja, das habe ich inzwischen auch nachgelesen. Schon wieder was neues gelernt :-))
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 ;-)
Man muss sich beim Bash-Scripting einfach immer wieder die Tatsache ins Gedächtnis rufen, dass die if-Abfrage einfach auf 0 / 1 reagiert. Auch der test (die [] sind auch nur ein Aufruf von test) liefert nur ein 0 oder != 0 zurück. Darum kann man beim Prüfen des Erfolgs eines Kommandos den test getrost weglassen, weil er in diesem Fall nur doppelt gemoppelt ist.
Das war mir schon klar. Der Grund für dieses Konstrukt von mir war, daß ich die Option '-q' von grep nicht kannte (siehe meine Antwort an Bernd). Ich habe ja eben nicht den Erfolg von grep getestet, sondern dessen Ausgabe der Anzahl von Fundstellen in der mount-Ausgabe.
und ich würde den Status abfragen! if ! mount /dev/hdb$devnum $MP_DIR/$mpoint; then echo Fehler beim mount exit 1 fi
Ist natürlich viel sauberer. Aber wenn das Gerät schon gemountet ist, ergibt 'echo $?' -> 32. In dem Fall soll das Skript weitermachen. Deshalb ist Deine Lösung hier auch etwas zu einfach. (Keine Angst, damit komme ich klar :-) )
Nein, wir befinden uns im Script hier im Zweig (siehe oben): if ! mount | grep -q /dev/hdb$devnum; then
Der Fall *schon gemountet* kann an dieser Stelle also nicht eintreten. Welchen Sinn hätte sonst die if-Afrage?
Ja klar, die if-Abfrage davor, ob das Gerät gemountet ist hatte ich an dieser Stelle schon wieder vergessen (knirsch :-\ ) Aber Bernd hat's auch nicht gemerkt ;-)
Danke nochmals für die nützlichen Hinweise. Kannst Du mir vielleicht ein Buch über die Bash empfehlen? Ich habe da eher an ein Nachschlagewerk gedacht, als an ein klassisches Lehrbuch, was man von vorne bis hinten durcharbeiten muß. Ich brauche eigentlich auch keine Hinweise über das Programmieren als solches, eher schon weitergehende Tips über effizientes Scripting mit der bash. Und das wichtigste: Einen guten und ausführlichen Index sollte es haben.
Im allgemeinen wird der Kofler (Michael Kofler: Linux - Installation, Konfiguration, Anwendung; Addison-Wesley; ISBN 3-8273-1475-5) als Einsteigerbuch empfohlen, der befasst sich aber nicht nur mit der bash.
Eine ältere Ausgabe davon habe ich, aber die bleibt trotz der vielen Seiten bei dem meisten Dingen ziemlich an der Oberfläche. Aber das war wohl Absicht. Stöbere mal bei O'Reilly rum, deren Titel
sind eigentlich immer zu empfehlen.
Ansonsten sind die im System vorhandenen Scripts eine gute Lektüre.
Das gilt, wie ich finde, für die wenigsten. Die meisten sind IMHO äußerst bescheiden bzw. gar nicht kommentiert. Wenn man dauernd damit befaßt ist, reicht einem (dem Autor) das vielleicht, aber anderen ... Bei vielen Skripten hat man nicht mal den Eindruck, daß die Bash hier eine Programmiersprache bereitstellt. Wenn man zum Beispiel irgendwelche nichtoffensichtlichen Zeichenmanipulationen vornimmt, sollte man wenigstens kommentieren, *was* man da tut, vielleicht auch *warum*. Das *wie* kann man getrost der Recherche des Lesers überlassen, denn die Syntax muß man eh lernen. Allerdings ist einiges schon recht tricky programmiert - um das zu verstehen muß man schon ein Bash-Kenner sein. Auch nicht gerade im Sinne der Transparenz und Wartbarkeit ... Gruß, Tom
On Sam, 03 Mai 2003 at 21:04 (+0200), Thomas Michalka wrote:
Jan Trippler schrieb: [...]
Das obige Konstrukt lebt ja davon, dass dem awk quasi ein { print $2 } *untergeschoben* wird. Man kann dem awk aber auch Shell-Variablen übergeben:
jan@k500:~> echo pos1 pos2 pos3 | awk ' { print $pos } ' pos=2 pos2
Das ist doch keine Parameterübergabe im eigentlichen Sinn, also kein Argument für awk. Die Zuweisung pos=2 gehört doch zur Bash. *Bevor* awk letztlich aufgerufen wird, wird schon in der Shell 'pos' durch '2' ersetzt, oder sehe ich das falsch?
Schau Dir mal folgendes Konstrukt dazu an: jan@k500:~/tmp> echo pos1 pos2 pos3 | awk ' { print $pos; pos=3; print $pos; print '$pos', pos; } ' pos=2 pos2 pos3 2 3 Es gibt einen Unterschied zwischen der awk-Variablen pos und der Shell-Variablen $pos, der letzte print zeigt das. Deshalb findet hier schon eine Parameter-Übergabe statt, es wird ja innerhalb des awk mit der awk- und nicht der Shell-Variablen gearbeitet. Das ist der Unterschied zur $'$pos' Variante, bei der ja praktisch der Teil '$pos' sich ausserhalb des awk befindet und durch die Shell ausgewertet wird. Die Zuweisung pos=2 ist keine bash-Zuweisung, sondern ein Argument des awk. Gerade ist mir noch ein besseres Beispiel eingefallen: jan@k500:~/tmp> pos=1 jan@k500:~/tmp> echo pos1 pos2 pos3 | awk ' { print $pos; pos=3; print $pos; print '$pos', pos; } ' pos=2 pos2 pos3 1 3 jan@k500:~/tmp> unset pos jan@k500:~/tmp> echo pos1 pos2 pos3 | awk ' { print $pos; pos=3; print $pos; print '$pos', pos; } ' pos=2 awk: cmd. line:1: { print $pos; pos=3; print $pos; print , pos; } awk: cmd. line:1: ^ parse error Du siehst also, dass die awk-Variable pos und die Shell-Variable $pos zwei Paar Schuhe sind. Die Verwirrung wäre vielleicht nicht so groß gewesen, wenn ich die Variablen unterschiedlich genannt hätte ;-) [...]
Ansonsten sind die im System vorhandenen Scripts eine gute Lektüre.
Das gilt, wie ich finde, für die wenigsten. Die meisten sind IMHO äußerst bescheiden bzw. gar nicht kommentiert. Wenn man dauernd damit befaßt ist, reicht einem (dem Autor) das vielleicht, aber anderen ... Bei vielen Skripten hat man nicht mal den Eindruck, daß die Bash hier eine Programmiersprache bereitstellt. Wenn man zum Beispiel irgendwelche nichtoffensichtlichen Zeichenmanipulationen vornimmt, sollte man wenigstens kommentieren, *was* man da tut, vielleicht auch *warum*. Das *wie* kann man getrost der Recherche des Lesers überlassen, denn die Syntax muß man eh lernen. Allerdings ist einiges schon recht tricky programmiert - um das zu verstehen muß man schon ein Bash-Kenner sein. Auch nicht gerade im Sinne der Transparenz und Wartbarkeit ...
*real programmers do not comment* ;-) Was ich meinte war, dass die im System vorhandenen Scripts einen großen Teil der Fähigkeiten der bash abdecken. Sie können natürlich kein Lehrbuch im klassischen Sinn abdecken und beim intensiven Auseinandersetzen mit fremdem Code lernt man IMHO am besten (auch wenn es mühsamer ist und länger dauert). Jan
Jan Trippler schrieb:
On Sam, 03 Mai 2003 at 21:04 (+0200), Thomas Michalka wrote:
Jan Trippler schrieb:
[...]
Das obige Konstrukt lebt ja davon, dass dem awk quasi ein { print $2 } *untergeschoben* wird. Man kann dem awk aber auch Shell-Variablen übergeben:
jan@k500:~> echo pos1 pos2 pos3 | awk ' { print $pos } ' pos=2 pos2
Das ist doch keine Parameterübergabe im eigentlichen Sinn, also kein Argument für awk. Die Zuweisung pos=2 gehört doch zur Bash. *Bevor* awk letztlich aufgerufen wird, wird schon in der Shell 'pos' durch '2' ersetzt, oder sehe ich das falsch?
Hier habe ich das richtige gemeint ("kein Argument für awk", s.u.), es aber völlig falsch dargestellt :-\ Das lag daran, daß mir nicht klar war, daß awk als Argument nach dem Programmtext neben Eingabedateien auch Variablenzuweisungen akzeptiert, sogar _anstatt_ einer Eingabedatei - awk liest dann eben von stdin, wie im Beispiel mit der Pipe dargestellt. Also - ich _hab's_ jetzt - in pos=2, das ist die awk-Variable pos, die awk setzt wird bevor es den Programmtext in den {...} auf die Eingabe von stdin anwendet. Aber was in diesem Beispiel von Dir definitiv nicht der Fall sein kann, ist, daß hier eine Shell-Variable an awk übergeben wird, weil $pos hier gar nicht ausgewertet wird. Allerdings hast Du Dich hier wohl nur verschrieben, weil Du das durch Deine Beispiele mit dem in der bash freien $pos nachher selber widerlegst.
Schau Dir mal folgendes Konstrukt dazu an: jan@k500:~/tmp> echo pos1 pos2 pos3 | awk ' { print $pos; pos=3; print $pos; print '$pos', pos; } ' pos=2 pos2 pos3 2 3
Wenn ich vor dieser Zeile in der bash nicht pos=2 setzte, dann gibt es natürlich auch hier den parse error beim Komma (s. Dein Bsp. unten), weil das in der bash _sichtbare_ $pos nichts liefert, denn in pos steht ja kein Text, solange pos in der bash nicht erstmals gesetzt wird. Also hat das letzte print-Statement von awk kein erstes Argument, was es übelnimmt.
Es gibt einen Unterschied zwischen der awk-Variablen pos und der Shell-Variablen $pos, der letzte print zeigt das. Deshalb findet hier schon eine Parameter-Übergabe statt, es wird ja innerhalb des awk mit der awk- und nicht der Shell-Variablen gearbeitet. Das ist der Unterschied zur $'$pos' Variante, bei der ja praktisch der Teil '$pos' sich ausserhalb des awk befindet und durch die Shell ausgewertet wird.
Das gilt sowohl für $'$pos' als auch für '$pos', weil in *beiden* Fällen das $pos in der Kommandozeile für die bash ungeschützt zur Auswertung bereitsteht. Das erste $ in $'$pos' steht ja innerhalb des ersten Hochkomma-Paars (das Beispiel ist hier leider nicht mehr sichtbar) und wird deshalb erst von awk interpretiert. Nun möchte ich aber begründen, warum hier für mich - formal gesehen - *keine Parameterübergabe* stattfindet. Allerdings wird es jetzt kompliziert für mich den logischen Sachverhalt sprachlich korrekt auszudrücken. Aber ich will es versuchen ... Der Unterschied zwischen der awk-Variablen pos und der bash-Variablen pos besteht darin, daß erstere erst belegt (pos=2 am Schluß) wird, nachdem awk gestartet wurde, während die bash schon bei der Interpretation der Kommandozeile das Konstrukt $pos durch den Textinhalt der letzteren ersetzt, d.h. $pos wird durch eine 2 ersetzt, wenn pos=2 in der bash vorher gesetzt wurde (kleine Verwirrung wirklich _nur_ wegen der Namensgleichheit der Variablen). Zum Beginn des awk-Laufs gibt es also schon kein $pos mehr, awk sieht nur noch eine simple 2, und print 2 in awk ergibt nun mal ... Es wird also in diesem Beispiel innerhalb des awk - zumindest an dieser Stelle - überhaupt nicht mit einer Variablen gearbeitet, sondern mit einer (Text-)Konstante. Da diese, wie schon ausgeführt, durch Textersatz der bash eingeführt wurde, sehe ich hier keinesfalls eine Parameterübergabe, sondern so etwas wie den Textersatz im C-Präprozessor, eingeleitet durch ein #define.[1] Als bekräftigendes Gegenbeispiel sei angeführt, daß awk durchaus Parameter übergeben bekommt, z.B. die Zuweisung pos=2 (!) am Schluß der Kommandozeile. Dies ist deshalb so, weil dieser Parameter in das Array ARGV[] (wohl argv[] in main() ) von awk übergeben wird. Obwohl der Programmtext in den geschweiften Klanmmern auch ein solches Argument für awk ist, würde ich nicht so weit gehen, daß ich unter dem Ersatz von $pos durch die bash innerhalb des awk-Programmtexts auch nur eine indirekte Parameterübergabe verstehen wollte.
Die Zuweisung pos=2 ist keine bash-Zuweisung, sondern ein Argument des awk.
Ok, das habe ich inzwischen erkannt, s.o.
Gerade ist mir noch ein besseres Beispiel eingefallen: jan@k500:~/tmp> pos=1 jan@k500:~/tmp> echo pos1 pos2 pos3 | awk ' { print $pos; pos=3; print $pos; print '$pos', pos; } ' pos=2 pos2 pos3 1 3
Das ist das Beispiel von oben, bloß daß Du vorher die bash-Variable pos mit dem Text "2" füllst.
jan@k500:~/tmp> unset pos jan@k500:~/tmp> echo pos1 pos2 pos3 | awk ' { print $pos; pos=3; print $pos; print '$pos', pos; } ' pos=2 awk: cmd. line:1: { print $pos; pos=3; print $pos; print , pos; } awk: cmd. line:1: ^ parse error
Ist klar, weil bei der awk-Ausführung das letzte print-Statement kein erstes Argument vor dem Komma hat (sorry, ich wiederhole mich), da in der bash die Variable pos vorher geleert wurde.
Du siehst also, dass die awk-Variable pos und die Shell-Variable $pos zwei Paar Schuhe sind. Die Verwirrung wäre vielleicht nicht so groß gewesen, wenn ich die Variablen unterschiedlich genannt hätte ;-)
Ja vielleicht. Aber so wie die awk-Variable eine awk-Variable bleibt, so bleibt die Shell-Variable eine Shell-Variable (auch als Platzhalter für Text innerhalb eines awk-Arguments) und kann gerade wegen dieser fomalen Abgrenzung gar nicht "übergeben" werden. Das geht nur mit vollständigen und echten Argumenten für awk, aber die bash - und das ist nichts neues - übergibt an *jedes* Programm, das sie startet alle auf der Kommandozeile eingegebenen _Argumente_ als solche. An dieser Stelle des besseren Verständnisses endlich angelangt, möchte ich Dir, aber auch den anderen Thread-Teilnehmern ein gaaanz herzliches Dankeschön für diese - wohl vor allem für mich - äußerst fruchtbare und lehrreiche Diskussion aussprechen. Jetzt mache ich mich gleich daran, meine etwas unsauberen scripting-hacks, die ich zuletzt verbrochen habe, in eine ordentliche Form zu bringen - nachdem ich mich ausgeschlafen habe, sonst kommt doch nur Schrott dabei raus :-)) - und deshalb jetzt "aus die Maus" und schönen Gruß allerseits, Tom [1] BTW: Wenn man es sehr formal sieht, tut die bash nie etwas anderes, wenn der Interpreter auf ein Konstrukt der Art $VAR trifft. Allerdings kann man auch die Sichtweise vertreten, daß VAR als echte (Text-) Variable zu verstehen ist, und das $-Zeichen als Operator für den Lesezugriff fungiert, denn man schreibt ja allgemein VAR2=$VAR1. Interessanterweise wäre dann das =-Zeichen hinter einer Variablen (VAR2=) konsequent als Schreibzugriffsoperator zu interpretieren.
On Die, 06 Mai 2003 at 01:47 (+0200), Thomas Michalka wrote:
Jan Trippler schrieb: [...]
jan@k500:~> echo pos1 pos2 pos3 | awk ' { print $pos } ' pos=2 pos2 [...] Aber was in diesem Beispiel von Dir definitiv nicht der Fall sein kann, ist, daß hier eine Shell-Variable an awk übergeben wird, weil $pos hier gar nicht ausgewertet wird. Allerdings hast Du Dich hier wohl nur verschrieben, weil Du das durch Deine Beispiele mit dem in der bash freien $pos nachher selber widerlegst.
Ja, die Finger waren schneller als das Auge ;-) Ich meinte es so: pos=2 echo pos1 pos2 pos3 | awk ' { print $pos } ' pos=$pos [...]
Es gibt einen Unterschied zwischen der awk-Variablen pos und der Shell-Variablen $pos, der letzte print zeigt das. Deshalb findet hier schon eine Parameter-Übergabe statt, es wird ja innerhalb des awk mit der awk- und nicht der Shell-Variablen gearbeitet. Das ist der Unterschied zur $'$pos' Variante, bei der ja praktisch der Teil '$pos' sich ausserhalb des awk befindet und durch die Shell ausgewertet wird.
Das gilt sowohl für $'$pos' als auch für '$pos', weil in *beiden* Fällen das $pos in der Kommandozeile für die bash ungeschützt zur Auswertung bereitsteht. Das erste $ in $'$pos' steht ja innerhalb des ersten Hochkomma-Paars (das Beispiel ist hier leider nicht mehr sichtbar) und wird deshalb erst von awk interpretiert.
Die Stellen meinte ich auch nicht. Logischerweise sind diese beiden Konstrukte Shell-Variablen, sie befinden sich ausserhalb des schützenden ''-Quotings und werden deshalb von der Shell _vor_ dem Start des awk substituiert. Wie Du weiter hinten richtig geschrieben hast, kriegt hier der awk nur $2 bzw. 2 zu sehen.
Nun möchte ich aber begründen, warum hier für mich - formal gesehen - *keine Parameterübergabe* stattfindet. Allerdings wird es jetzt kompliziert für mich den logischen Sachverhalt sprachlich korrekt auszudrücken. Aber ich will es versuchen ...
Doch, das ist eine astreine Parameter-Übergabe, Du bist nur an der falschen Stelle (den print habe ich nur eingebaut, um die Unterschiede zu verdeutlichen). Nochmal die Readers-Digest-Version (mit eindeutigen Variablennamen): shellpos=2 echo erstes_argument zweites_argument drittes_argument | \ awk ' { print "Auswertung von $shellpos = '$shellpos'", "; Argument = ", $'$shellpos'; print "und nun awkpos = ", awkpos, "; Argument = ", $awkpos; awkpos --; print "der Beweis = ", awkpos, "; Argument = ", $awkpos; } ' awkpos=$shellpos ^^^^^^^^^^^^^^^^ das hier ist die Parameter-Übergabe
Der Unterschied zwischen der awk-Variablen pos und der bash-Variablen pos besteht darin, daß erstere erst belegt (pos=2 am Schluß) wird, nachdem awk gestartet wurde, während die bash schon bei der Interpretation der Kommandozeile das Konstrukt $pos durch den Textinhalt der letzteren ersetzt, d.h. $pos wird durch eine 2 ersetzt, wenn pos=2 in der bash vorher gesetzt wurde (kleine Verwirrung wirklich _nur_ wegen der Namensgleichheit der Variablen).
Zum Beginn des awk-Laufs gibt es also schon kein $pos mehr, awk sieht nur noch eine simple 2, und print 2 in awk ergibt nun mal ...
ACK
Es wird also in diesem Beispiel innerhalb des awk - zumindest an dieser Stelle - überhaupt nicht mit einer Variablen gearbeitet, sondern mit einer (Text-)Konstante.
Da diese, wie schon ausgeführt, durch Textersatz der bash eingeführt wurde, sehe ich hier keinesfalls eine Parameterübergabe, sondern so etwas wie den Textersatz im C-Präprozessor, eingeleitet durch ein #define.[1]
Ja, aus awk-Sicht stimmt das. Im obigen Beispiel wird der awk so gestartet: awk ' { print "Auswertung von $shellpos = 2", "; Argument = ", $2; print "und nun awkpos = ", awkpos, "; Argument = ", $awkpos; awkpos --; print "der Beweis = ", awkpos, "; Argument = ", $awkpos; } ' awkpos=2 Wie man hier auch schön sehen kann, wird in der 1. print-Anweisung der Ausdruck $shellpos _nicht_ ersetzt, er ist nämlich durch die ''-Quotes vor der Shell geschützt.
Als bekräftigendes Gegenbeispiel sei angeführt, daß awk durchaus Parameter übergeben bekommt, z.B. die Zuweisung pos=2 (!) am Schluß der Kommandozeile.
Und genau das meinte ich. [...]
Gerade ist mir noch ein besseres Beispiel eingefallen: jan@k500:~/tmp> pos=1 jan@k500:~/tmp> echo pos1 pos2 pos3 | awk ' { print $pos; pos=3; print $pos; print '$pos', pos; } ' pos=2 pos2 pos3 1 3
Das ist das Beispiel von oben, bloß daß Du vorher die bash-Variable pos mit dem Text "2" füllst.
Nein, ich setze die Shell-Variable auf 1, die _awk_Variable wird auf 2 gesetzt. Jan
participants (3)
-
B.Brodesser@t-online.de
-
Jan.Trippler@t-online.de
-
Thomas Michalka