in BASH for schleife mit 2 laufvariablen

Post Reply
Message
Author
User avatar
killerhippy
Posts: 529
Joined: 19. May 2000 19:36
Contact:

in BASH for schleife mit 2 laufvariablen

#1 Post by killerhippy »

Hi Bashscripter,

mir geht's darum, in einer Variable mehrere zusammengehörige Paare als doubles in einer for schleife zu verwenden.
Das advanced Bash-scripting Howto liefert ein prima Beispiel, das auch genauso funktioniert, wie ich es verwenden möchte.

allerdings möchte ich die Paare in einer Variable im Kopf des Listings definieren, damit man es easy editieren kann und neue Paar editieren kann, ohne die for schleife finden zu müssen.

<blockquote><pre><font size="1" face="">code:</font><hr><font face="Courier New" size="2">
Wie definiert man planets im Kopf des listings, sodass set $x trotzdem richtig expandiert wird?
Meine Versuche schlugen fehl.
Soll also in etwa so ausssehen:

#!/bin/bash
# Planets revisited.
planets='"Mercury 36" "Venus 67" "Earth 93" "Mars 142" "Jupiter 483"'
...
for x in $planets; do
set $x
echo "$1 $2,000,000 miles from the sun"
...

wie macht man's?

---schnipp---
Example 3-27. for loop with two parameters in each [<!--no-->list<!--no-->] element

#!/bin/bash
# Planets revisited.

# Want to associate name of each planet with its distance from the sun.

for planet in "Mercury 36" "Venus 67" "Earth 93" "Mars 142" "Jupiter 483"
do
set $planet # Parses variable "planet" and sets positional parameters.
# May need to save original positional parameters, since they get overwritten.
echo "$1 $2,000,000 miles from the sun"
#-------two tabs---concatenate zeroes onto parameter $2
done

exit 0
---schnapp---
</font><hr></pre></blockquote>
Last edited by killerhippy on 13. Dec 2002 18:22, edited 3 times in total.
Es gibt keine dumme Fragen!

Killerhippy

rattengift

Re: in BASH for schleife mit 2 laufvariablen

#2 Post by rattengift »

<blockquote><pre><font size="1" face="">code:</font><hr><font face="Courier New" size="2">
IFS=","
planet="Mercury 36,Venus 67,Earth 93,Mars 142,Jupiter 483"
for object in $planet
do
IFS=" "
set $object
echo "$1 $2,000,000 miles from the sun"
done
exit 0</font><hr></pre></blockquote>ps: IFS ist der internal field separator. nicht sehr schön, aber so tuts. ich würd ja eher 2 arrays verwenden (einen für den namen und einer für die distanz, der code wäre sauberer).

User avatar
killerhippy
Posts: 529
Joined: 19. May 2000 19:36
Contact:

Re: in BASH for schleife mit 2 laufvariablen

#3 Post by killerhippy »

Hi rattengift,

vielen Dank für den Tip, das ist genau das, was ich brauche.

...wie stellste dir das in zwei arrays vor? AFAIK bzw. zwei schleifen systeme kenne, wird der äußeren laufvariable jede innere laufvariable gegengehalten. oder wie geht das ohne for schleife?

übrigens ist die paarweise einrichtigung in der variable im scriptkopf sinniger, weil ich an einem iptables-script bastele und da soll das dann so aussehen:

SERVICES="pop-3 <ip-pophost>, smtp <ip-smtphost>, ftp, http, ssh <ip-trustedhost>"

und das funzt mit der IFS variable super.
Es gibt keine dumme Fragen!

Killerhippy

rattengift

Re: in BASH for schleife mit 2 laufvariablen

#4 Post by rattengift »

> ...wie stellste dir das in zwei arrays vor?

hm, mit IFS ist vielleicht doch besser als mit arrays, man kann es auch noch etwas übersichtlicher machen; du kannst übrigens auch mehrere trennzeichen zulassen (siehe code).
mit arrays spart man sich zwar das IFS-gefummle, braucht aber eine zählervariable. das lohnt sich, wenn man nicht nur die ganze liste abarbeiten, sondern auch mal über die position direkt auf ein bestimmtes element zugreifen will (je nachdem ob du das brauchst).
<blockquote><pre><font size="1" face="">code:</font><hr><font face="Courier New" size="2">variante mit IFS

#!/bin/bash

IFS="," # trenne bei ","
SERVICES="pop-3 (ip-pophost), smtp (ip-smtphost), ssh (ip-trustedhost)"

for object in $SERVICES
do
IFS=" ()" # trenne bei " ", "(" und ")"
set $object
echo "$1: $2"
done
#IFS=" "
exit 0


mit arrays:


#!/bin/sh
SERVICE=(pop-3 smtp ssh)
SERV_IP=(ip1 ip2 ip3)

c=0
while [ "${SERVICE[c]}" != "" ]; do
printf "${SERVICE[c]}: ${SERV_IP[c]}\<!--no-->n"
c=$[ c + 1 ]
done

exit 0</font><hr></pre></blockquote>ob das so nun besser ist, hängt natürlich von deiner anwendung ab.

Jochen

Re: in BASH for schleife mit 2 laufvariablen

#5 Post by Jochen »

Kleiner Gag am Rande, rattengift: Deine erste Variante würde so auch in der alten Bourne-Shell laufen. also dürfte die erste Zeile des Skripts auch "#!/bin/sh" lauten. Deine zweite Variante nutzt Arrays, die die alte Bourne-Shell nicht kennt. Daher sollte hier "#!/bin/bash" stehen... <img src="http://www.pl-forum.de/UltraBoard/Images/Happy.gif" border="0" align="middle">

IFS zu ändern ist nicht ganz ohne, da IFS an einigen Stellen von der Shell verwendet wird, u.a. beim Zerlegen der Skript-Zeilen selbst. Wenn man es nicht in einem sehr kleinen, überschaubaren Skript macht, würde ich aus Vorsicht immer so vorgehen:<blockquote><pre><font size="1" face="">code:</font><hr><font face="Courier New" size="2">
OLD_IFS="$IFS"
IFS="<i>mein-neues-trennzeichen</i>"
# Mache etwas, wozu IFS geändert sein muss
IFS="$OIFS"
</font><hr></pre></blockquote>

Und zu den Arrays muss man nicht unbedingt händisch eine Zählervariable mitführen, wenn man ein bisschen tricksen kann: Man verwende die Arrays auf 1 basiert, d.h. mit 1 als kleinstem Index, und lückenfrei:<pre>declare -a ARR=([<!--no-->1]=eins zwei drei)</pre>Dann kann man mittels seq eine for-schleife über alle Elemente basteln:<pre>for I in $(seq 1 ${#ARR[<!--no-->*]}) ; do echo $I ${ARR[<!--no-->$I]} ; done</pre>Das zweite Argument zu seq ergibt die Anzahl der Elemente in einem Array.

Die Schreibweise ist vielleicht nicht sehr eingänglich, aber man kannn nicht mehr vergessen, den Schleifenzähler hochzuzählen...

Jochen

User avatar
killerhippy
Posts: 529
Joined: 19. May 2000 19:36
Contact:

Re: in BASH for schleife mit 2 laufvariablen

#6 Post by killerhippy »

@rattengift:

mein <ip-soundso> geraffel sollte nur ein Platzhalter für eine echte IP sein. :)

aber trotzdem danke, das kann ja für andere scripte von Nutzen sein!

@Jochen:

vor lauter mp3 hören und bier saufen (immerhin ist ja wochenende) kann ich z.Z. deine Intelligenz nicht nachvollziehen, zumal ich eigentlich DOD spiele inzwischen...

aber morgen, so UltraBord-v1.6.1 will, kann ich ja morgen mit kater noch mal nachlesen...

thanx-so-much
Es gibt keine dumme Fragen!

Killerhippy

rattengift

Re: in BASH for schleife mit 2 laufvariablen

#7 Post by rattengift »

Hi Jochen,

> Daher sollte hier "#!/bin/bash" stehen...

richtig. ich schreib aus alter gewohnheit eben "sh", und weil das zeug bei mir sowieso unter der bash läuft, hab ich noch gar nicht gemerkt, dass das in manchen fällen nicht gut ist. übrigens: ein macosX-user erzählte mir, dass man in osX die shellscripts einfach mit "#!sh" einleitet. eigentlich einleuchtend, sh ist ja sowieso im pfad, und falls es dann doch mal nicht in /bin liegen sollte, ist man auf der sicheren seite.

> OLD_IFS="$IFS"; ...; IFS="$OIFS"

ACK. ich hatte es ursprünglich drin, dachte dann aber: warum so ein kleines script unnötig aufblähen?! aber du hast schon recht: besser ein backup machen.

> declare -a ARR=([<!--no-->1<!--no-->]=eins zwei drei)

raffiniert. allerdings handelt man sich mit dem "seq" eine äussere abhängigkeit (portabilität!) ein, anstatt auf bash-builtins zu vertrauen. der code wird auch nicht unbedingt lesbarer (jemand, der zb nur pascal kennt, wird die while-schleife ohne probleme verstehen, das seq muss er erst mal nach-man'en). vor allem aber bringe ich es als alter C-(usw)-programmierer nicht übers herz, einen array bei irgendetwas anderem als 0 beginnen zu lassen! <img src="http://www.pl-forum.de/UltraBoard/Images/Happy.gif" border="0" align="middle">
also ich würde die alte, ehrliche while-schleife mit laufvariable vorziehen. oder als kompromiss:<blockquote><pre><font size="1" face="">code:</font><hr><font face="Courier New" size="2">declare -a SERVICE=(pop-3 smtp ssh) # 0..(n-1), wie es sich gehört
declare -a SERV_IP=(ip1 ip2 ip3) #
for I in $(seq 1 ${#SERVICE[*]}); do printf "${SERVICE[I-1]}: ${SERV_IP[I-1]}\<!--no-->n"; done</font><hr></pre></blockquote>frage: dem array-index hast du "$" vorangestellt: ARR[$I]. ich dachte erst, es sei falsch, hab dann aber gemerkt, dass *beides* geht. gibt es dafür eine erklärung? (normalerweise ist man ja froh, wenn es *überhaupt* geht, und hier geht nun beides?? kopfschüttel...).

> Das zweite Argument zu seq ergibt die Anzahl der Elemente in einem Array.

hm, ich habe in man bash sowas gesucht, es aber wohl übersehen. das kann man in die while-bedingung einbauen anstatt dort (unschön) die array-elemente abzufragen.
<font face="Courier New">while [ $c -lt ${#SERVICE[*]} ]; do</font><!--fixed-->

@sascha

> mein <ip-soundso> geraffel sollte nur ein Platzhalter für eine echte IP sein.

schon klar, aber wieso, was hab ich denn damit angestellt? <img src="http://www.pl-forum.de/UltraBoard/Images/Happy.gif" border="0" align="middle">

Jochen

Re: in BASH for schleife mit 2 laufvariablen

#8 Post by Jochen »

> allerdings handelt man sich mit dem "seq" eine äussere abhängigkeit (portabilität!) ein,

Das ist sicherlich richtig. Ist aber alles Ansichtssache: Ein für unsere Zwecke ausreichendes Minimal-seq ist mit bash-Mitteln schnell erstellt:<blockquote><pre><font size="1" face="">code:</font><hr><font face="Courier New" size="2">
function loop { # Jetzt mal loop benannt, damit es sich nicht mit seq beisst
case $# in
1) START=1; END=$1 ; DIS=1 ;;
2) START=$1; END=$2 ; DIS=1 ;;
3) START=$1; END=$2 ; DIS=$3 ;;
*) echo "Usage: $0 endvalue|startvalue endvalue [displacement]" >&2; exit 1;;
esac

while (( START <= END )) ; do
echo $START
(( START += DIS ))
done
}
</font><hr></pre></blockquote>Man sollte jetzt auch die Parameter prüfen usw., aber für's erste reicht. Allerdings gebe ich Dir gerne recht, dass bei der Array-Deklaration die Leserlichkeit nicht wirklich gut ist und dass auch bei mir ein 1-basiertes Array ein gewisses Unwohlsein hervorruft. Eigentlich habe ich es nur genommen, um nicht <pre>for I in $( seq 1 (( ${#ARR[*]} - 1 )) ) ; do echo $I ${ARR[$I]} ; done</pre> schreiben zu müssen. Damit ist der Endwert einmal im Schleifenkopf berechnet. Ansonsten muss bei jedem Schleifendurchlauf der Index um 1 angepasst werden - viel zu viel Aufwand... <img src="http://www.pl-forum.de/UltraBoard/Images/Happy.gif" border="0" align="middle">

> frage: dem array-index hast du "$" vorangestellt: ARR[$I]. ich dachte erst, es sei falsch, hab dann aber gemerkt,
> dass *beides* geht. gibt es dafür eine erklärung? (normalerweise ist man ja froh, wenn es *überhaupt* geht,
> und hier geht nun beides?? kopfschüttel...).

<img src="http://www.pl-forum.de/UltraBoard/Images/Happy.gif" border="0" align="middle"> Die Erklärung ist ganz einfach. Stell Dir vor, Du möchtest $1 als Index zu einem Array verwenden. Wenn Du nun das $-Zeichen weglässt, wäre aber über das Literal "1" immer das gleiche Element angesprochen... Da Du aber unzweideutig im Array-Kontext bist, muss eine Folge von Zeichen ein Variablenname sein. Die Shell ist dann eben so freundlich und denkt sich das "$" davor. Auch bei Rechnungen geht es so: Ob Du nun <pre>(( START += DIS ))</pre> oder <pre>(( $START += $DIS ))</pre> schreibst, ist egal.

> hm, ich habe in man bash sowas gesucht, es aber wohl übersehen.

Ja, der Teil ist schwer zu übersehen, owohl er da steht, wo er hingehört, nämlich unter "Arrays".<blockquote><hr> ${#name[subscript]} expands to the length of ${name[sub-
script]}. If subscript is * or @, the expansion is the
number of elements in the array. Referencing an array
variable without a subscript is equivalent to referencing
element zero.<hr></blockquote>

Ich schlage es auch manchmal nach, da ich es nicht jeden Tag benötige und überlese es dabei noch, obwohl ich <i>weiss</i>, wo es steht... <img src="http://www.pl-forum.de/UltraBoard/Images/Happy.gif" border="0" align="middle">

Jochen

rattengift

Re: in BASH for schleife mit 2 laufvariablen

#9 Post by rattengift »

Hi Jochen,

> Ein für unsere Zwecke ausreichendes Minimal-seq ist mit bash-Mitteln schnell erstellt:

mhh, allmählich beschleicht mich das gefühl, dass die erste fassung (mit IFS) vielleicht doch die einfachste ist <img src="http://www.pl-forum.de/UltraBoard/Images/Happy.gif" border="0" align="middle">

> Die Erklärung ist ganz einfach.

leuchtet ein. danke!

> > hm, ich habe in man bash sowas gesucht, es aber wohl übersehen.
> da steht, wo er hingehört, nämlich unter "Arrays".

genau da hab ich es auch gesucht, es aber trotzdem übersehen. ein nachprüfen soeben ergab, dass es aber bei mir tatsächlich auch da steht. der abschnitt ist (selbst für manpage-verhältnisse) reichlich übersichtlich.

Post Reply