Warum muss ich den gelesenen Befehl in eine Subshell einfügen, während ich die Pipeline [duplicate] verwende?

Nov 30 2020

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|ywird die Ausgabe von xauf die Eingabe von umleiten y. Warum read a; read a b; echo $akann die Eingabe nicht abgerufen werden, aber eine Subshell kann?

Antworten

8 muru Dec 01 2020 at 03:10

Das Hauptproblem hierbei ist die korrekte Gruppierung der Befehle. Unterschalen sind ein sekundäres Problem.

x|yleitet den Ausgang von xzum Eingang von umy

Ja, aber x | y; zdie Ausgabe von wird nicht xan beide yund umgeleitet z.

In df . | read a; read a b; echo "$a"verbindet sich die Pipeline nur df .und read adie anderen Befehle haben keine Verbindung zu dieser Pipeline. Sie müssen die reads 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 echoBefehl muss sich also in derselben Unterschale wie der reads 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.

3 glennjackman Dec 01 2020 at 03:10

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}' )
2 StephenHarris Dec 01 2020 at 03:11

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 aBefehl 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 bashund die rechte Seite einer Pipeline, auf der eine Subshell ausgeführt wird, sodass die $a $bWerte außerhalb der whileSchleife nicht zugänglich sind , aber das ist ein anderes Problem!