Lesen Sie in der Bash, nachdem eine Pipe keine Werte festgelegt hat

22

Edit: Originaltitel lautete "Lesen schlägt in Bash fehl"

Mit ksh verwende ich read, um Werte bequem zu trennen:

$ echo 1 2 3 4 5 | read a b dump
$ echo $b $a 
2 1
$

Aber es schlägt fehl in bash:

$ echo 1 2 3 4 5 | read a b dump
$ echo $b $a 

$

Ich habe in der Manpage keinen Grund gefunden, warum dies fehlschlägt. Irgendeine Idee?

Emmanuel
quelle
2
Dies wird (etwas dunkel) auf Seite 024 von Greg's Bash FAQ besprochen .
Scott

Antworten:

27

bash Führt die rechte Seite einer Pipeline in einem Subshell-Kontext aus , sodass Änderungen an Variablen (was auch immer der readFall ist) nicht beibehalten werden. Sie sterben am Ende des Befehls ab, wenn die Subshell dies tut.

Stattdessen können Sie die Prozessersetzung verwenden :

$ read a b dump < <(echo 1 2 3 4 5)
$ echo $b $a
2 1

In diesem Fall readwird in unserer primären Shell ausgeführt, und unser Befehl zur Ausgabeerzeugung wird in der Subshell ausgeführt. Die <(...)Syntax erstellt eine Subshell und verbindet ihre Ausgabe mit einer Pipe, die wir readmit der normalen <Operation in die Eingabe von umleiten . Da es readin unserer Haupt-Shell lief, sind die Variablen korrekt gesetzt.

Wie in einem Kommentar erwähnt, können Sie einen Here-String verwenden , wenn Sie eine Zeichenfolge buchstäblich in Variablen aufteilen möchten :

read a b dump <<<"1 2 3 4 5"

Ich nehme an, dass es mehr als das gibt, aber dies ist eine bessere Option, wenn es keine gibt.

Michael Homer
quelle
3
Oder sogar read a b dump <<< '1 2 3 4 5'.
Choroba
Danke an alle, übrigens habe ich bemerkt, dass mksh (auf cygwin) dasselbe macht wie bash.
Emmanuel
@Michael Homer Gute Erklärung! Ich fand ein anderes Beispiel , das , dass jeder Befehl in der Pipeline in eigenem Subshell lief erklären kann: cat /etc/passwd | (read -r line ; echo $line). Aber der nächste, echoder $linenicht in der Pipeline ist, bringt nichts auf den Bildschirm, weil der Wert nur zwischen Klammern (Subshell) existierte. Hoffe, es hilft jemandem.
Yurij Goncharuk
17

Dies ist kein bashFehler, da POSIXbeide bashund kshVerhaltensweisen zulässig sind, was zu der unglücklichen Diskrepanz führt, die Sie beobachten.

http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_12

Darüber hinaus befindet sich jeder Befehl einer Multi-Command-Pipeline in einer Subshell-Umgebung. Als Erweiterung können jedoch einige oder alle Befehle in einer Pipeline in der aktuellen Umgebung ausgeführt werden. Alle anderen Befehle sollen in der aktuellen Shell-Umgebung ausgeführt werden.

Mit bash 4.2und neuer können Sie jedoch die lastpipeOption in nicht interaktiven Skripten festlegen , um das erwartete Ergebnis zu erhalten, z.

#!/bin/bash

echo 1 2 3 4 5 | read a b dump
echo before: $b $a 
shopt -s lastpipe
echo 1 2 3 4 5 | read a b dump
echo after: $b $a 

Ausgabe:

before:
after: 2 1
jlliagre
quelle
1
+1 danke für die "lastpipe" info. Entschuldigung für die Verspätung
Emmanuel
1
Das Problem dabei lastpipeist, dass es in anderen Shells (zB Bindestrich) nicht funktioniert. Grundsätzlich gibt es keine Möglichkeit, dieses tragbare System so
schnell
1
@anarcat Dies ist korrekt, aber die hier gestellte Frage betraf Bash.
Juli
@anarcat: Dies kann sich in Zukunft ändern, da es einen Wunsch gibt, POSIX aus einem anderen Grund zu ändern (PIPE-Status). Siehe hier: unix.stackexchange.com/questions/476834/… Das Ändern anderer Shells ist nicht trivial, es hat mehrere Monate gedauert den Parser und Interpeter auf der Bourne Shell (bosh) neu zu schreiben, um das schnellere Verhalten des modernen ksh zu implementieren.
Schily