Verwenden Sie den eingebauten Bash-Lesevorgang ohne while-Schleife

8

Ich bin an bashdie eingebaute readFunktion von while-Schleifen gewöhnt , z.

echo "0 1
      1 1
      1 2
      2 3" |\
while read A B; do
    echo $A + $B | bc;
done

Ich habe an einem makeProjekt gearbeitet, und es wurde ratsam, Dateien zu teilen und Zwischenergebnisse zu speichern. Infolgedessen zerlege ich oft einzelne Zeilen in Variablen. Während das folgende Beispiel ziemlich gut funktioniert,

head -n1 somefile | while read A B C D E FOO; do [... use vars here ...]; done

Es ist irgendwie dumm, weil die while-Schleife nie mehr als einmal ausgeführt wird. Aber ohne die while,

head -n1 somefile | read A B C D E FOO; [... use vars here ...]

Die gelesenen Variablen sind immer leer, wenn ich sie benutze. Ich habe dieses Verhalten nie bemerkt read, weil ich normalerweise while-Schleifen verwenden würde, um viele ähnliche Zeilen zu verarbeiten. Wie kann ich bashdas readeingebaute System ohne eine while-Schleife verwenden? Oder gibt es eine andere (oder noch bessere) Möglichkeit, eine einzelne Zeile in mehrere (!) Variablen einzulesen?

Fazit

Die Antworten lehren uns, es ist ein Problem des Scoping. Die Aussage

 cmd0; cmd1; cmd2 | cmd3; cmd4

wird so interpretiert, dass die Befehle cmd0, cmd1und cmd4in dem gleichen Umfang ausgeführt werden, während die Befehle cmd2und cmd3jeweils ihre eigenen Subshell gegeben und damit verschiedene Bereiche. Die ursprüngliche Shell ist das übergeordnete Element beider Subshells.

Bananguin
quelle
Zu Ihrer Schlussfolgerung : (1) Die ursprünglich dargestellte Befehlszeile ist syntaktisch falsch. Insbesondere … cmd1; | cmd2 …ist ein Fehler. (2) Wir können das sagen cmd2und bekommen cmd3jeweils ihre eigene Unterschale, cmd1; cmd2 | cmd3; cmd4indem wir Dinge wie answer=42 | sleep 0oder ausprobieren cd other/dir | read foo. Sie werden sehen, dass sich der erste Befehl in jeder Pipeline in einer Subshell befinden muss, da sie den Hauptprozess (übergeordnete Shell) nicht beeinflussen.
Scott

Antworten:

8

Dies liegt daran, dass der Teil, in dem Sie die Vars verwenden, ein neuer Befehlssatz ist. Verwenden Sie stattdessen Folgendes:

head somefile | { read A B C D E FOO; echo $A $B $C $D $E $FOO; }

Beachten Sie, dass in dieser Syntax ein Leerzeichen nach dem { und ein ;(Semikolon) vor dem stehen muss }. Auch -n1ist nicht notwendig; readliest nur die erste Zeile.

Zum besseren Verständnis kann dies hilfreich sein. es macht das gleiche wie oben:

read A B C D E FOO < <(head somefile); echo $A $B $C $D $E $FOO

Bearbeiten:

Es wird oft gesagt, dass die nächsten beiden Aussagen dasselbe tun:

head somefile | read A B C D E FOO
read A B C D E FOO < <(head somefile)

Nun, nicht genau. Die erste ist ein Rohr aus headzu bash‚s readbuiltin. Der Standard eines Prozesses ist der Standard eines anderen Prozesses.

Die zweite Anweisung ist Umleitung und Prozessersetzung. Es wird von bashselbst gehandhabt . Es erstellt ein FIFO (Named Pipe <(...)), mit dem headdie Ausgabe verbunden ist, und leitet es ( <) an den readProzess weiter.

Bisher scheinen diese gleichwertig zu sein. Bei der Arbeit mit Variablen kann dies jedoch von Bedeutung sein. Im ersten Fall werden die Variablen nach der Ausführung nicht gesetzt. Im zweiten Fall sind sie in der aktuellen Umgebung verfügbar.

Jede Shell hat in dieser Situation ein anderes Verhalten. Siehe den Link, für den sie sind. In können bashSie dieses Verhalten mit Befehlsgruppierung {}, Prozessersetzung ( < <()) oder Here-Zeichenfolgen ( <<<) umgehen .

Chaos
quelle
{}Ich habe es nur versucht (). Warum startet das ;in Ihrem zweiten Beispiel nicht "einen neuen Befehlssatz"? Eigentlich verstehe ich das < <Konstrukt überhaupt nicht.
Müssen
@ Bananguin siehe meine Bearbeitung, hoffe, es bringt Licht in dieses Problem.
Chaos
2
Ich glaube, ich habe es verstanden: Der eigentliche Punkt ist, dass Bash bei der Verwendung von Rohren Unterschalen für jeden Prozess der Pipeline erstellt und aufgrund des Umfangs die Variablen nach der Pipeline verschwunden sind. Mit using {}wird der Rest der Zeile zu einem (Eins!) - Element der Pipeline, während innerhalb dieser Unterschale die Variablen verfügbar sind. Bei der Prozesssubstitution werden keine Pipes verwendet und daher bashkeine Subshells erzeugt.
Bananguin
@ Bananguin du hast es ^^
Chaos
1
Da readnur die erste Eingabezeile gelesen wird, ist nicht nur -n1die redundante, sondern der gesamte headBefehl. Wenn Sie den headBefehl entfernen, können Sie auch die Pipeline entfernen. Dann wird es read A B C D E FOO < somefileund es wird keine Unterschale, so dass die Variablen für den nächsten Befehl verfügbar bleiben.
Kasperd
3

Um aus einem sehr nützlichen Artikel zu zitieren : wiki.bash-hackers.org :

Dies liegt daran, dass die Befehle der Pipe in Subshells ausgeführt werden, die die übergeordnete Shell nicht ändern können. Infolgedessen werden die Variablen der übergeordneten Shell nicht geändert (siehe Artikel: Bash und der Prozessbaum ).

Da die Antwort jetzt einige Male gegeben wurde, ist eine alternative Möglichkeit (unter Verwendung nicht eingebauter Befehle ...) folgende:

$ eval `echo 0 1 | awk '{print "A="$1";B="$2}'`;echo $B $A
$ 1 0
geedoubleya
quelle
Mein Problem war, dass ich dachte, Bash würde alles zwischen dem Pipe-Symbol und dem Ende der Zeile in dieselbe Unterschale setzen, aber anscheinend liest es nur die nächste Anweisung.
Bananguin
Große Frage ...
Geedoubleya
0

Wie Sie bemerkt haben, bestand das Problem darin, dass eine Pipe to readin einer Subshell ausgeführt wird.

Eine Antwort ist, einen Heredoc zu verwenden :

numbers="01 02"
read first second <<INPUT
$numbers
INPUT
echo $first
echo $second

Diese Methode ist insofern hilfreich, als sie sich in jeder POSIX-ähnlichen Shell genauso verhält.

Eradman
quelle