Forum: PC-Programmierung Mini Bash Programm - wo steckt der Fehler ?


von Jürgen W. (lovos)


Lesenswert?

1
test="0";
2
echo "Start"
3
4
echo -e "1\n2\n3\n4\n5" | while read line; do
5
6
  echo "1. test=$test" "line=$line"
7
8
  echo -e "a\nb\nc" | while read l; do
9
    if [ $line = 2 ] || [ $line = 4 ]; then
10
      test="1";
11
      echo "2. test=$test" "line=$line, l=$l"
12
    fi
13
14
  done
15
16
done

Die erste Schleife looped durch die Werte 1,2,3,4,5 (jeweils durch 
Newline getrennt, Variable "line"). Die Schleifenvariable "l" der 
inneren Schleife nimmt die Werte "a","b","c" an.
Die Variable "test" wird ganz am Anfang auf "0" gesetzt.
Nimmt "line" den Wert 2 oder 4 an, wird "test" auf 1 gesetzt.

Ich wuerde erwarten, dass "test" auf "1" bleibt bis zum Ende.
Aber die Ausgabe ist so:
1
Start
2
1. test=0 line=1
3
1. test=0 line=2
4
2. test=1 line=2, l=a
5
2. test=1 line=2, l=b
6
2. test=1 line=2, l=c
7
1. test=0 line=3
8
1. test=0 line=4
9
2. test=1 line=4, l=a
10
2. test=1 line=4, l=b
11
2. test=1 line=4, l=c
12
1. test=0 line=5

Nimmt "line" einen naechsten Wert an, dann wird auch "test" auf 0 
gesetzt. Aber warum nur? (keine Testaufgabe, ich bin wirklich ratlos)

von Thomas P. (tpircher) Benutzerseite


Lesenswert?

Die Variable "test" wird nicht auf null gesetzt. Was passiert ist, dass 
eine while Schleife in einer Untershell ausgefuehrt wird. Du kannst den 
Wert einer Variable eines Elternprozesses nicht in einem Kindprozess 
ueberschreiben, deshalb ist "test" beim Verlassen der inneren Schleife 
wieder (immer noch) auf dem alten Wert.

EDIT: vielleicht etwas verstaendlicher ausgedrueckt: in einer While 
Schleife arbeitest du mit Kopien der Variablen die vorher gesetzt 
wurden. Beim Verlassen einer Schleife werden diese Kopien verworfen und 
das Script arbeitet mit den alten Variablen weiter.

Thomas

von Jürgen W. (lovos)


Lesenswert?

Danke, das Raetsel ist geloest.
Aber wie kann ich im Inneren einer Schleife mir einen Zustand "merken", 
den ich am Schleifenende brauche?
Abruch kommt nicht in Frage, sie muss komplett abgearbeitet werden.

Es ginge natuerlich, den Zustand in eine temporaere Datei zu speichern, 
z.B.
1
echo "1" >/tmp/bashvariable

Und spaeter
1
if [ `cat /tmp/bashvariable` = "0" ]; then


Aber das erscheint mir keine saubere Loesung.

von Klaus W. (mfgkw)


Lesenswert?

Es wird nicht prinzipell eine Subshell für eine Schleife gestartet.
Aber hier geht es um eine Pipe:
1
echo -e "a\nb\nc" | while read l; do...
, daher die Subshell.

Es geht einfacher mit for:
1
for l in a b c; do...

Die andere Schleife entsprechend...

von Jürgen W. (lovos)


Lesenswert?

>Es wird nicht prinzipell eine Subshell für eine Schleife gestartet.
>Aber hier geht es um eine Pipe:

Ja, auch mit Arrays geht es
1
array1=( "1" "2" "3" "4" )
2
array2=( "a" "b" "c" )
3
num1=${#array1[@]};
4
num2=${#array2[@]};
5
test="0";
6
7
for ((i=0;i<$num1;i++)); do
8
  for ((j=0;j<$num2;j++)); do
9
     if [ $i = 2 ]; then test="1"; fi
10
     echo ${array1[$i]} ${array2[$j]} "test=$test"
11
  done
12
done

Leider liegen meine Daten bereits als Textzeilen vor, die ich in ein 
Array umwandeln muesste.

von Thomas P. (tpircher) Benutzerseite


Lesenswert?

Wie Klaus geschrieben hat, kannst du eine for Schleife verwenden oder 
die Ausgabe mit ackticks in eine Variable schreiben:
1
res=`echo -e "1\n2\n3\n4\n5" | while read line; do
2
    # do something
3
    echo $test
4
done`
5
echo $res

oder mit einer Pipe an eine weiteres Programm oder Schleife 
weiterreichen.
1
echo -e "1\n2\n3\n4\n5" | while read line; do
2
    # do something
3
    echo $test
4
done | sed -e -'p'

von Micky (Gast)


Lesenswert?

Da ja "test" eine eingebaute Funktion der BASH ist, empfiehlt es sich 
generell, diesen Namen für eigene Zwecke NICHT zu benutzen. Benenn das 
mal um, zB in "Probe". Ich versprech hier keine Wunder, aber es könnte 
was nützen...

von Jürgen W. (lovos)


Lesenswert?

Der uebersichtlichste Ansatz waere die Umwandlung der Textzeilen in ein 
Array. Das gleiche Problem. Funktioniert nicht, da wegen der Pipe eine 
Subshell angelegt wird.
1
i=0;
2
arr=()
3
echo -e "1\n2\n3" | while read line; do
4
  arr[$i]=$line
5
  echo "$i:" ${arr[$i]}
6
  i=$(($i+1))
7
done
8
echo "1.Elem:" ${arr[0]}
9
exit
10
11
Start
12
0: 1
13
1: 2
14
2: 3
15
1.Elem:

Hat jemand einen Vorschlag, um NL-separierte Daten in ein Array zu 
wandeln?

von Jürgen W. (lovos)


Lesenswert?

schon was im Internet gefunden.
Ich finde die Sachen erst, NACHDEM ich eine Frage im Forum stelle.
1
declare -a array1
2
arr=($(echo -e "1\n2\n3" | tr '\n' ' '))
3
echo "1.Elem:" ${arr[0]}
4
exit

von Thomas P. (tpircher) Benutzerseite


Lesenswert?

Bist du dir sicher dich nicht in etwas verrannt zu haben? Jedes mal wenn 
ich ein aehnliches Problem hatte konnte durch Nachdenken eine wesentlich 
einfachere und elegantere Loesung gefunden werden.

Und falls das Problem wirklich kompliziert ist, dann ist die Shell 
vielleicht nicht das geeignete Mittel zum Problem. sed/awk oder auch 
Python koennten besser dafuer geeignet sein.

Willst du uns dein eigentliches Problem schildern, dann koennte dir 
wahrscheinlich am ehesten geholfen werden.

von Jürgen W. (lovos)


Lesenswert?

Was ganz banales.
Ich hole vom Internet eine Textdatei.
Dann sollen von dieser Textdatei alle Zeilen auf der konsole ausgegeben 
werden, die einen von mehreren Begriffen enthalten.
Diese Begriffe sind als Array vorgegeben.

Z.B.
Textdatei:
1
Muenchen
2
Berlin
3
Cuxhafen
4
Frankfurt am Main


Begriffe:
1
Nuernberg
2
Augsburg
3
Muenchen


Danach soll nur
1
Muenchen

ausgegeben werden.
Die Textdatei wie das Begriffe-Array kann Leerzeichen enthalten.
Es darf NICHT nach Leerzeichen separiert werden.
Deshalb funktioniert obiger Ansatz
1
arr=($(echo -e "1 1\n2\n3" | while read l; do echo "$l"; done ))

NICHT. Denn er macht aus der ersten Zeile "1 1" zwei Array-Elemente

EDIT:
Wenn Eintraege mehrfach vorhanden sind, soll nur eine Ausgabe erfolgen, 
an der Stelle des ersten Eintrages.
So ganz einfache Loesungen wie grep fallen dann schon weg.

von Thomas P. (tpircher) Benutzerseite


Lesenswert?

Ist die Reihenfolge wichtig? Wenn nicht, dann sollte so etwas 
funktionieren:
1
$ cat FILE1
2
Muenchen
3
Berlin
4
Cuxhafen
5
Frankfurt am Main
6
7
$ cat FILE2
8
Nuernberg
9
Augsburg
10
Muenchen
11
12
$ sort FILE1 FILE2 | uniq -d
13
Muenchen

von Klaus W. (mfgkw)


Lesenswert?

ich denke, das ist einfach genug und schnell:
1
#!/bin/bash
2
#
3
# Time-stamp: "16.09.09 18:01 t.sh klaus?wachtler.de"
4
5
DATEI_AUS_INTERNET=dateiausinternet.txt
6
ARRAY_BEGRIFFE=( "Frankfurt am Main" "Nuernberg" )
7
8
# einmalig aus allen Begriffen einen String machen; darin die
9
# Begriffe durch | getrennt:
10
BEGRIFFE_ALS_STRING=${ARRAY_BEGRIFFE[0]}
11
for ((i=1;i<${#ARRAY_BEGRIFFE[@]};i++)); do
12
    BEGRIFFE_ALS_STRING=$BEGRIFFE_ALS_STRING"|"${ARRAY_BEGRIFFE[$i]}
13
done
14
15
# in $BEGRIFFE_ALS_STRING steht jetzt z.B. Frankfurt am Main|Nuernberg.
16
# Das ist ein (extended, nicht basic) regulärer Ausdruck, auf den 
17
# alle Zeilen passen, die "Frankfurt am Main" oder "Nuernberg"
18
# enthalten.
19
20
# Jetzt mit grep alle Zeilen der Datei ausgeben, die einen der Bergriffe enthalten:
21
grep -E "$BEGRIFFE_ALS_STRING" $DATEI_AUS_INTERNET

Wenn die Datei so aussieht:
1
Muenchen ist bayrisch
2
Berlin ist fett
3
nass ist es in Cuxhafen
4
und Frankfurt am Main ist hoch
5
Frankfurt an der Oder ist weit weg
6
Frankfurt am Rhein gibt es nicht
7
Nuernberg ist schoen
dann sieht die Ausgabe so aus:
1
klaus@i4a:~ > ./t.sh
2
und Frankfurt am Main ist hoch
3
Nuernberg ist schoen

von Jürgen W. (lovos)


Lesenswert?

Ja, die Reihenfolge ist wichtig.

Ich habe jetzt an den IFS rumgebastelt und die Umwandlung in ein Array 
funktioniert jetzt, auch das uebrige Programm.

Allerdings braucht das Programm zur Ausfuehrung ein paar Sekunden und 
der Luefter meines Notebooks laeuft los. Das heisst richtige 
Rechenleistung.

Schon interessant, wie Rechenleistungs-Intensiv der Bash-Interpreter 
laeuft.

von Klaus W. (mfgkw)


Lesenswert?

Wenn man ihn mit Schleifen quält...

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.