Mini Bash Programm - wo steckt der Fehler ?

#1415036
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)
Persönliche Seite #1415052
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
#1415072
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.
#1415082
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.
Persönliche Seite #1415087
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'
#1415117
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?
Persönliche Seite #1415133
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.
#1415160
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.
#1415190
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
#1415191
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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren