• Willkommen im Linux Club - dem deutschsprachigen Supportforum für GNU/Linux. Registriere dich kostenlos, um alle Inhalte zu sehen und Fragen zu stellen.

Phänomen mit while

regexer

Advanced Hacker
Code:
#!/bin/bash

treffer=no

echo 'a' | while read string   # Versuch 1
# for string in a                # Versuch 2
# string=a ; while [ 1 = 1 ]     # Versuch 3
do
  if [ $string = "a" ]
  then
    treffer=yes
    echo $treffer
  fi
  break
done
echo $treffer
Anscheinend bewirkt die echo-while-Kombination, dass $treffer eine innerhalb der while-Schleife eine lokale Varialbe ist. Dieses Verhalten ist mir völlig neu. Versuch Nr. 3 zeigt, dass das while ansich nicht "schuld" ist. Die Korn-Shell unter SCO UnixWare verhält sich übrigens nicht so. Komisch!!! Hat jemand dafür eine Erklärung? Oder noch besser: Wie kann ich $treffer ganz normal als globale Variable behandeln?
 

gaw

Hacker
Das ist aber so weil in Befehlen mit asynchronen Befehlen z.B mit einer pipe die Anweisung in einer Subshell ausgeführt wird. Steht auch in der man bash:
Code:
A command invoked  in  this  separate  environment  cannot
       affect the shell's execution environment.

       Command substitution and asynchronous commands are invoked
       in a subshell environment that is a duplicate of the shell
       environment,  except  that  traps  caught by the shell are
       reset to the values that the shell inherited from its par­
       ent  at  invocation.  Builtin commands that are invoked as
       part of a pipeline are also executed in a  subshell  envi­
       ronment.   Changes made to the subshell environment cannot
       affect the shell's execution environment.

Es gibt keine globalen Variabeln in deinem Sinne so dass du in einer Bash von einer Subshell auf die Variabeln der aufrufenden Schell zugreifen kannst. Du kannst sie in eine temoräre Datei abpeichern oder ein alternatives Design anwenden, zum Beispiel:
Code:
# 4 Zeilen String, 2 beginnen mit A
string="Aller \nAnfang \nist\nschwer"
# Welche haben A am Anfang?
pattern="^A"  

$COUNT_TARGET=`echo -e $string | grep $pattern | wc -l`

oder so ähnlich.
mfG
gaw
 
A

Anonymous

Gast
Deine Variable treffer ist innerhalb der whileschleife bekannt und auch ihr Wert gesetzt, aber es ist nicht die gleiche Variable (gleicher Speicherplatz im Speicher) sondern nur eine Kopie davon, also wenn du ihren Wert innerhalb der Schleife veränderst, dann ist nach Abschluss der Schleife wieder der alte Wert da. Das Ganze hat was mit Prozesskommunikation und Kommandogruppierung zu tun und wie die Shell die jeweiligen Prozesse organisiert, und wie du schon selbst bemerkt hast, nicht alle Shells stellen sich hier so widerspinstig an wie die bash unter LINUX. Versuche die while Schleife als erstes Kommando hinter ein ; oder neu Zeile anzuordnen dann sollte es gehen. Dieses kannst du erreichen indem du vorher wie bei Versuch 3 die Eingaben für die Whileschleife in eine Variable abspeicherst oder du legst die Eingaben in eine Datei zB eingabedatei und machst es folgend.

Code:
#!/bin/bash
treffer=no

while read string                # Versuch 1
do
  if [ $string = "a" ]
  then
    treffer=yes
    echo $treffer
  fi
  break
done <   eingabedatei
echo $treffer

Mit ein wenig mehr Kenntnissen in der shellprogrammierung sind dann noch mehr Tricks und andere Umleitungen möglich.

hoffe das hilft dir erst einmal weiter.

robi
 
OP
regexer

regexer

Advanced Hacker
gaw schrieb:
mit einer pipe die Anweisung in einer Subshell ausgeführt wird
Dachte ich erst auch. Aber Subshell heisst Subprocess. Dann probiere mal folgendes:

Code:
#!/bin/bash 

treffer=no 

echo 'a' | while read string   # Versuch 1 
# for string in a                # Versuch 2 
# string=a ; while [ 1 = 1 ]     # Versuch 3 
do 
  if [ $string = "a" ] 
  then 
    treffer=yes 
    echo $treffer 
    echo $$
  fi 
  break 
done 
echo $treffer
echo $$

Gut, gell? Oder stehe ich einfach auf dem Schlauch...
 

gaw

Hacker
Ich würde mich nicht darauf verlassen dass die echo $$ Anweisung zeitkritisch ohne Pufferung ausgeführt wird. Außerdem ist nicht immer gesagt wann und wie die bash eine Subshell ausführt, das hängt damit zusammen wie die Befehle intern gruppiert werden. Zeitkritische Multiprozess- oder Mulithreadabläufe werden auch nicht unbedingt mit Skripten programmiert. Vorsicht ist auf jeden Fall geboten.
Eine Subshell wird auf jeden Fall dann ausgeführt wenn sich die Pipe innerhalb der Schleife befindet. Bei komplexen Befehlen würde ich davon ausgehen, das dies passieren kann. Ob dann echo $$ so rasch gepuffert wird, ist halt die Frage. Dein Zähler funktioniert zum Beispiel so:

Code:
#!/bin/bash

treffer=1

echo -e "a\na\na\na\nb\na\nb" | while read i;
do
  if [ "$i" == "a" ];
  then
    treffer=$((treffer +1));
    echo $treffer;
  fi
done

echo $treffer
ganz zufriedenstellend, obwohl ich wc -l bevorzuge. Dadurch spare ich mir die zeitaufwendige while Schleife mit einzelnen Subshellanweisungen. Hier wird die ganze Schleife nicht als lokale Subshell angelegt.

Ansonsten ist mir nicht ganz klar was das mit dem echo soll und worauf du hinauswillst. Warum sollte man eine einzelne Zeile per echo durch eine pipe schicken und mit while read auffangen?

mfG
gaw
 

oc2pus

Ultimate Guru
so ginge es ...
notoxp schrieb:
Code:
#!/bin/bash 
treffer=no 

echo 'a' | while read string   # Versuch 1 
# for string in a                # Versuch 2 
# string=a ; while [ 1 = 1 ]     # Versuch 3 
do 
  if [ $string = "a" ] 
  then 
    treffer2=yes 
    echo $treffer 
    echo $treffer2
    treffer=$treffer2  # damit wird der "äussere Wert geschrieben" 
    echo $$
  fi 
  break 
done 
echo $treffer
echo $$

http://www.tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_02.html
Kapitel 3.2.3
 

oc2pus

Ultimate Guru
gaw schrieb:
Ansonsten ist mir nicht ganz klar was das mit dem echo soll und worauf du hinauswillst. Warum sollte man eine einzelne Zeile per echo durch eine pipe schicken und mit while read auffangen?

ich denke ihm geht es nur um das Problem, wie die Variablen sichtbar werden in den einzelnen Blöcken. Und um es zu vereinfachen hat er es auf das "wesentliche" reduziert ...
 
A

Anonymous

Gast
oc2pus schrieb:
ich denke ihm geht es nur um das Problem, wie die Variablen sichtbar werden in den einzelnen Blöcken.

Hi,
nimmer so ganz neu die Problmematik ;-)
http://www.linux-forum.biz/forum.php?step=viewthread&forumid=12&threadid=53
Ich glaub dort hab ich nicht zum ersten, aber zum letzen mal das Problem gesehen #fg#
http://www.google.com/search?hl=de&q=loop+variable+pipe&btnG=Google-Suche&meta=
Da sind mindestens 20 Treffer zu dem Thema LOL
Hatt schon ein bisserl nen Bart #grins#

Mƒg ®êïñï
 
OP
regexer

regexer

Advanced Hacker
oc2pus schrieb:
Und um es zu vereinfachen hat er es auf das "wesentliche" reduziert ...
So ist es. Die eigentliche Aufgabe wurde mitlerweile auf eine andere Weise gelöst. Nur verstanden hatte ich es nicht.
nimmer so ganz neu die Problmematik ;-)
Für mich schon, weil ich die Shell-Programmierung unter DEC UNIX erlernt habe.
Was mich stutzig machte, ist dass die Kornshells der verschiedenen Unix-Derivate unterschiedlich reagierten. Aber das ist nicht der erste Unterschied, den ich zwischen Linux, SCO und TRU64 entdeckt habe ...
 
OP
regexer

regexer

Advanced Hacker
oc2pus schrieb:
Code:
#!/bin/bash 
treffer=no 

echo 'a' | while read string   # Versuch 1 
# for string in a                # Versuch 2 
# string=a ; while [ 1 = 1 ]     # Versuch 3 
do 
  if [ $string = "a" ] 
  then 
    treffer2=yes 
    echo $treffer 
    echo $treffer2
    treffer=$treffer2  # damit wird der "äussere Wert geschrieben" 
    echo $$
  fi 
  break 
done 
echo $treffer
echo $$
Sorry, aber das funktioniert bei mir (SLES8 ) nicht.
 
OP
regexer

regexer

Advanced Hacker
gaw schrieb:
Code:
#!/bin/bash

treffer=1

echo -e "a\na\na\na\nb\na\nb" | while read i;
do
  if [ "$i" == "a" ];
  then
    treffer=$((treffer +1));
    echo $treffer;
  fi
done

echo $treffer
Funktioniert leider auch nicht ...
 

oc2pus

Ultimate Guru
hm, strange ...
ich habe es auf einer SuSE 9.2 mit bash-3.0-8.2 ausprobiert.

SuSE-9.1 und SuSE-8.2 mit
bash --version
GNU bash, version 2.05b.0(1)-release (i586-suse-linux)
Copyright (C) 2002 Free Software Foundation, Inc.
geht ebenfalls nicht ...
 

oc2pus

Ultimate Guru
man-pages sind immer "Stiefkinder" und evtl nicht up-to-date

probier mal bash --version

meine Version (auf der 9.2)
bash --version
GNU bash, version 3.00.0(1)-release (i586-suse-linux)
Copyright (C) 2004 Free Software Foundation, Inc.
 
A

Anonymous

Gast
Ohne Wort ;-)
-----------------------------schnipp---------------------------
#!/bin/bash
treffer=1
echo $treffer out $$
ps -C `basename $0`
echo 'a' | while read string
do
if [ $string = "a" ]
then
treffer=2
echo $treffer in $$
ps -C `basename $0`
fi
break
done
echo $treffer nacher $$
ps -C `basename $0`
------------------------------------schnapp------------------------------

Mƒg ®êïñï
 

oc2pus

Ultimate Guru
jo, siehe oben meinen Nachtrag, sowohl SuSE-8.2 als auch 9.1 mit bash 2.05b "fressen" meine gepostete Version nicht, die bash-3.x nimmt es.

Toll, wie immer steckt der Teufel im Versionsdetail :)
 
OP
regexer

regexer

Advanced Hacker
reini123 schrieb:
Ohne Wort ;-)
-----------------------------schnipp---------------------------
#!/bin/bash
treffer=1
echo $treffer out $$
ps -C `basename $0`
echo 'a' | while read string
do
if [ $string = "a" ]
then
treffer=2
echo $treffer in $$
ps -C `basename $0`
fi
break
done
echo $treffer nacher $$
ps -C `basename $0`
------------------------------------schnapp------------------------------

Mƒg ®êïñï
Na sowas! Ich dachte ich hatte das abgecheckt. Mit sleep 10 und zeitgleichem ps ... Na dann ist alles klar eigentlich ...
 
OP
regexer

regexer

Advanced Hacker
So geht es übrigens auf jeden Fall - eigentlich einfach:
Code:
#!/bin/bash
echo 'a' | while read string
do
  if [ $string = "a" ]
  then
    echo treffer
    break
  fi
done | grep treffer >/dev/null
if [ $? = 0 ]
then
  echo TREFFER
else
  echo KEIN TREFFER
fi
 
Oben