Frage bash: Variable verliert Wert am Ende der Leseschleife


Ich habe ein Problem in einem meiner Shell-Skripte. Frage ein paar Kollegen, aber alle schütteln nur den Kopf (nach einigem Kratzen), also bin ich hier um eine Antwort zu bekommen.

Nach meinem Verständnis sollte das folgende Shell-Skript "Count is 5" als letzte Zeile ausgeben. Außer es nicht. Es wird "Anzahl ist 0" ausgegeben. Wenn das "while read" durch eine andere Art von Schleife ersetzt wird, funktioniert es gut. Hier ist das Skript:

Echo "1"> Eingabedaten
echo "2" >> Eingabedaten
echo "3" >> Eingabedaten
echo "4" >> eingabedaten
Echo "5" >> Eingabedaten

CNT = 0

cat input.data | während gelesen;
tun
  lasse CNT ++;
  echo "Zählen zu $ ​​CNT"
erledigt
echo "Anzahl ist $ CNT"

Warum passiert das und wie kann ich das verhindern? Ich habe dies in Debian Lenny und Squeeze versucht, das gleiche Ergebnis (z. B. Bash 3.2.39 und Bash 4.1.5. Ich gebe zu, dass ich kein Shell-Script-Wizard bin, also würden alle Hinweise geschätzt.


31
2018-04-13 17:07


Ursprung




Antworten:


Siehe Argument @ Bash FAQ Eintrag # 24: "Ich setze Variablen in einer Schleife. Warum verschwinden sie plötzlich, nachdem die Schleife beendet wurde? Oder, warum kann ich keine Daten zum Lesen leiten?" (Zuletzt archiviert Hier).

Zusammenfassung: Dies wird nur von bash 4.2 und höher unterstützt. Wenn Sie bash verwenden, müssen Sie anstelle einer Pipe verschiedene Methoden wie Befehlssubstitutionen verwenden.


27
2018-04-13 17:08



Sie erhalten den Bonus, da Ihre Antwort mir die größte Auswahl an Optionen bietet. - wolfgangsz
Die Verbindung ist tot. Aus diesem Grund sind Link-Only-Antworten schlecht. Fassen Sie die Antwort hier zumindest zusammen. - rudolfbyker
Gott, noch eine andere Zeit, in der Ksh einfach so viel besser ist ... warum, warum sind alle in der Nähe von bash umhergelaufen? - Florian Heigl
@FlorianHeigl: Behauptest du, dass ksh die One True Shell ist? - Ignacio Vazquez-Abrams
@ IgnacioVazquez-Abrams nein, aber ich behaupte, dass die While-Loop-Behandlung in bash ist eine schrecklich PITA. Das Schleifenhandling war der Langläufer, der es davon abhielt, sich an die Funktionalität von 1993 zu binden. Die anderen Dinge sind getopt handling, wo die (auch 1993) eingebaute Handler einfach und fähig war, etwas, das Sie immer noch nicht erhalten, wenn Sie docopt verwenden. Ich behaupte, dass Bash sich seit über 20 Jahren hinter die Kurve stellt, und die Zeit, die THIS THING HIER oder Millionen von schlechten getopts verwendet werden, ist über das Maß hinaus - nur akzeptiert, weil die meisten Leute es nie erfahren werden. - Florian Heigl


Das ist ein "allgemeiner" Fehler. Pipes erstellen SubShells, also die while read läuft auf einer anderen Shell als dein Skript, das macht deine CNT Variable ändert sich nie (nur die in der Pipe-Subshell).

Gruppe die letzte echo mit der Unterschale while um es zu reparieren (es gibt viele andere Möglichkeiten, es zu beheben, das ist eins. Iains und Ignacios Antworten haben andere.)

CNT=0

 cat input.data | ( while read 
do
  let CNT++;
  echo "Counting to $CNT"
done 
echo "Count is $CNT" )

Lange Erklärung:

  1. Du erklärst CNT auf Ihrem Skript, um Wert 0 zu sein;
  2. Eine SubShell wird auf dem gestartet | zu while read;
  3. Ihre $CNT Die Variable wird in die SubShell mit dem Wert 0 exportiert.
  4. Die SubShell zählt und erhöht die CNT Wert bis 5;
  5. SubShell endet, Variablen und Werte werden zerstört (sie gelangen nicht zurück zum aufrufenden Prozess / Skript).
  6. Sie echo dein Original CNT Wert von 0.

29
2018-04-13 17:09



Das erste Shell-Skript, das ich jemals geschrieben habe, gab mir die gleichen Probleme, knallte meinen Kopf eine Weile gegen die Wand, bevor ich herausfand, dass diese Pfeifen zusätzliche Schalen spawnen. Jede Variable, mit der du in einer Pipe herumkommst, wird den Rahmen verlassen, sobald die Pipe endet - das heißt, wenn du wirklich etwas mit einer Variablen außerhalb der Pipe tun willst, in der sie benutzt wurde, musst du halte den Zustand durch etwas Funky wie eine temporäre Datei. - photoionized
Ausgezeichnete Antwort, leider kann ich nur einen Akzeptanzbonus geben. Es tut uns leid. - wolfgangsz


Das funktioniert

CNT=0 

while read ;
do
  let CNT++;
  echo "Counting to $CNT"
done <input.data
echo "Count is $CNT"

8
2017-08-31 19:01



Ich mag das, weil Sie wissen, wo die benötigten Daten sind und sie nur zurückbekommen müssen. Wenn Sie keine hoch qualifizierten Lösungen kennen, können Sie immer "eine Datei lesen" hahahha. +1 für dich. - erm3nda
Wenn Sie dies lesen, beachten Sie, dass die von Iain zur Verfügung gestellte Lösung nur funktioniert, wenn Sie Ihr Skript explizit bash aufrufen, indem Sie die erste Zeile: #! / Bin / bash haben und dass: #! / Bin / sh nicht funktioniert. - Roadowl
Interessant, erstes Beispiel, das ich je gesehen habe, wo Nutzlose Verwendung von Cat tatsächlich verhindert Code funktioniert. Übrigens @Roadowl, der einzige Bashismus hier ist die Linie let CNT++ was sollte stattdessen sein CNT="$((CNT+1))" Gebrauch machen von POSIX-konforme arithmetische Erweiterung. Der Rest ist bereits tragbar. - Wildcard


Versuchen Sie stattdessen, die Daten in einer Subshell zu übergeben, als wäre es eine Datei vor der while-Schleife. Dies ähnelt der Lösung von lain, geht jedoch davon aus, dass Sie keine intermittierende Datei möchten:

total=0
while read var
do
  echo "variable: $var"
  ((total+=var))
done < <(echo 45) #output from a command, script, or function
echo "total: $total"

4