Warum muss ich den gelesenen Befehl in eine Subshell einfügen, während ich die Pipeline [duplicate] verwende?
Der Befehl df .
kann uns zeigen, auf welchem Gerät wir uns befinden. Zum Beispiel,
me@ubuntu1804:~$ df .
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/sdb1 61664044 8510340 49991644 15% /home
Jetzt möchte ich den String bekommen /dev/sdb1
.
Ich habe es so versucht, aber es hat nicht funktioniert: df . | read a; read a b; echo "$a"
Dieser Befehl gab mir eine leere Ausgabe. Aber df . | (read a; read a b; echo "$a")
wird wie erwartet.
Ich bin jetzt etwas verwirrt.
Ich weiß, dass (read a; read a b; echo "$a")
das eine Unterschale ist, aber ich weiß nicht, warum ich hier eine Unterschale machen muss. Nach meinem Verständnis x|y
wird die Ausgabe von x
auf die Eingabe von umleiten y
. Warum read a; read a b; echo $a
kann die Eingabe nicht abgerufen werden, aber eine Subshell kann?
Antworten
Das Hauptproblem hierbei ist die korrekte Gruppierung der Befehle. Unterschalen sind ein sekundäres Problem.
x|y
leitet den Ausgang vonx
zum Eingang von umy
Ja, aber x | y; z
die Ausgabe von wird nicht x
an beide y
und umgeleitet z
.
In df . | read a; read a b; echo "$a"
verbindet sich die Pipeline nur df .
und read a
die anderen Befehle haben keine Verbindung zu dieser Pipeline. Sie müssen die read
s gruppieren : df . | { read a; read a b; }
oder df . | (read a; read a b)
damit die Pipeline mit beiden verbunden wird.
Jetzt kommt jedoch das Problem mit der Unterschale: Befehle in einer Pipeline werden in einer Unterschale ausgeführt, sodass das Festlegen einer Variablen in ihnen keine Auswirkungen auf die übergeordnete Shell hat. Der echo
Befehl muss sich also in derselben Unterschale wie der read
s befinden. Also : df . | { read a; read a b; echo "$a"; }
.
Nun, ob Sie hier verwenden ( ... )
oder { ...; }
keinen besonderen Unterschied machen, da die Befehle in einer Pipeline ohnehin in Subshells ausgeführt werden.
Eine Alternative ist die Verwendung einer Prozesssubstitution :
{ read header; read filesystem rest; } < <(df .)
echo "$filesystem"
Die <(...)
Prozessersetzung führt das enthaltene Skript (in einer Subshell) aus, verhält sich jedoch wie ein Dateiname. Sie müssen also zuerst <
den Inhalt (der die Ausgabe des Skripts darstellt) in das umklammerte Skript umleiten. Die gruppierten Befehle werden im aktuellen Regal ausgeführt.
Es kann schwierig sein, dies lesbar zu machen, aber Sie können beliebige Leerzeichen in die geschweiften Klammern und die Prozessersetzung einfügen.
{
read header
read filesystem rest
} < <(
df .
)
echo "$filesystem"
Und es könnte einfacher sein, ein externes Tool zum Extrahieren des Dateisystems zu verwenden:
filesystem=$( df . | awk 'NR == 2 {print $1}' )
Dein erster Befehl
df . | read a; read a b; echo "$a"
wird effektiv interpretiert als
( df . | read a ) ; read a b; echo "$a"
Die Pipeline wird also nur in den read a
Befehl eingespeist.
Da Sie mehrere Lesevorgänge aus der Pipeline wünschen, müssen Sie die Befehle zusammenfassen.
Jetzt muss es keine Unterschale sein; es könnte eine Gruppierung sein ..
bash-4.2$ df | { read a ; read a b ; echo $a ; }
devtmpfs
Häufiger möchten Sie möglicherweise eine Schleife
bash-4.2$ df | while read a > do > read a b > echo $a
> done
devtmpfs
tmpfs
/dev/vda3
/dev/vdb
Es gibt ein sekundäres Problem mit bash
und die rechte Seite einer Pipeline, auf der eine Subshell ausgeführt wird, sodass die $a
$b
Werte außerhalb der while
Schleife nicht zugänglich sind , aber das ist ein anderes Problem!