2013-08-29 19 views
7

erstes Drehbuch:Warum die Pipe das aktuelle Arbeitsverzeichnis zurücksetzt?

$ { mkdir dir; cd dir; pwd; } | cat; pwd; 
./dir 
. 

Zweite Skript:

$ { mkdir dir; cd dir; pwd; }; pwd; 
./dir 
./dir 

Warum diese | cat Auswirkungen auf aktuelles Verzeichnis hat? Und wie löst man es? Ich brauche das erste Skript, das genauso funktioniert wie das zweite. Ich möchte nicht cat mein aktuelles Verzeichnis zurück zu . ändern.

+2

http://stackoverflow.com/questions/5760640/left-side-of-pipe-is-the-subshell, Ihre 'cd' läuft in einer Subshell. – Mat

+0

Im zweiten Skript ist es auch in der Subshell – yegor256

+6

@ yegor256 Nein, in der zweiten ist es ** nicht ** in einer Subshell. – devnull

Antwort

11

vom manual Zitiert:

Jeder Befehl in einer Pipeline wird in einer eigenen Subshell ausgeführt (siehe Command Execution Environment).

Siehe auch Grouping Commands:

{}

{ list; } 

eine Liste von Befehlen zwischen geschweiften Klammern Plazierung bewirkt, daß die Liste in der aktuellen Shell Kontext ausgeführt wird. Keine Untershell wird erstellt.

0

Die Pipe setzt das aktuelle Arbeitsverzeichnis nicht zurück. Das Rohr erstellt Subhells, in Bash eine für jede Seite des Rohres. Die Subshell setzt auch das aktuelle Arbeitsverzeichnis nicht zurück. Die Subshell enthält die Änderungen an der Umgebung selbst und gibt sie nicht an die Parent-Shell weiter.

In Ihrem ersten Skript:

$ { mkdir dir; cd dir; pwd; } | cat; pwd; 

Die cd innerhalb des linken Unterschale des Rohres ausgeführt wird. Das Arbeitsverzeichnis wird nur in dieser Untershell geändert. Das Arbeitsverzeichnis der übergeordneten Shell wird nicht geändert. Die erste pwd wird in der gleichen Subshell wie die cd ausgeführt, so dass es das geänderte Arbeitsverzeichnis liest. Die pwd am Ende wird in der Parent-Shell ausgeführt, so dass das Arbeitsverzeichnis der Parent-Shell gelesen wird, das nicht geändert wurde.

In Ihrem zweiten Skript:

$ { mkdir dir; cd dir; pwd; }; pwd; 

Es gibt keine Sub-Shell erstellt. Die geschweiften Klammern erstellen keine Untershell. Die cd wird in der Parent-Shell ausgeführt und beide pwd s lesen das geänderte Arbeitsverzeichnis.

Weitere Informationen und Erklärungen lesen Sie das Klein bash Handbuch zu:

1

Das Verhalten der Pipeline-Befehle in Bezug auf Variablen ist Implementierung definiert.

Sie verwenden bash, die jede Komponente in eine Hintergrundshell setzen, so dass der cd Effekt in der Haupt-Shell verloren geht.

Wenn Sie ksh ausgeführt haben, die das letzte Element einer Pipeline in der aktuellen Shell behalten und Sie die cd in die letzte Anweisung gesetzt haben, wäre das Verhalten das erwartet worden.

$ bash 
$ { mkdir -p dir; cd dir; pwd; } | { cat; mkdir -p dir; cd dir; pwd ; } ; pwd; 
/tmp/dir 
/tmp/dir 
/tmp 
$ ksh 
$ { mkdir -p dir; cd dir; pwd; } | { cat; mkdir -p dir; cd dir; pwd ; } ; pwd; 
/tmp/dir 
/tmp/dir 
/tmp/dir 
2

Es ist nicht, dass das Rohr in das Verzeichnis zurückgeht, dann ist es, dass Sie den ersten Befehl gemacht haben (vor dem Semikolon) nur auf den cat Befehl. Sie leiten im Wesentlichen die Ausgabe des Unterprozesses von mkdir und cd und pwd an den Prozess cat.

Zum Beispiel: { mkdir dir; cd dir; pwd; } | cat; pwd;

Zuerst dehnt sich in zwei Verfahren: 1) { mkdir dir; cd dir; pwd; } | cat; und 2) pwd

Der erste Prozess expandiert in zwei Prozesse, die dann ihre { mkdir dir; cd dir; pwd; }stdout zu stdin von cat sendet. Wenn die erste dieser beiden Prozesse beendet und die stdout gesammelt, seine subprocess Ausfahrten und es ist wie die cd noch nie passiert, weil die cd nur das Verzeichnis des Prozesses beeinflusst darin ausgeführt wurde. Die pwd nie wirklich $PWD geändert, es nur gedruckt das, was auf stdin bereitgestellt wurde.

dieses Problem zu beheben (vorausgesetzt, ich verstehe, was Sie versuchen zu tun) ich dies ändern würde:

{ mkdir dir; cd dir; pwd; }; pwd; cd -

+1

Ihre vorgeschlagene Ersetzung wird nicht funktionieren, da 'cat' unendlich lange auf ein 'EOF' auf seiner' stdin' wartet, das es nicht erhalten wird . – sanmiguel

+2

Auch Ihre Erklärung ist nicht ganz richtig - die LHS der ** Pipe ** wird in einer Subshell ausgeführt. Sonst, '{mkdir dir; CD-Verzeichnis; pwd; } | {Katze; pwd;} würde für beide Aufrufe von "pwd" zu demselben "$ PWD" führen. – sanmiguel

+0

Sie haben beide Recht. Sanmiguel ist besonders so. Entschuldigung bitte :) – manchicken

2

Wenn Sie laufen:

{ mkdir -p dir; cd dir; pwd; } | cat; pwd 

ODER

{ mkdir -p dir; cd dir; pwd; } | date 

ODER

{ mkdir -p dir; cd dir; pwd; } | ls 

Sie führen Gruppe von Befehlen auf LHS von Rohr in einer Unterschale und damit change dirnicht im aktuell Shell nach beiden Befehlen (LHS und RHS) komplett reflektiert.

Allerdings, wenn Sie laufen:

{ mkdir -p dir; cd dir; pwd; }; pwd; 

Es gibt keine Pfeife ist zwischen daher alle Befehle in geschweiften Klammern und pwd außerhalb geschweifte Klammer Lauf in der aktuellen Shell selbst damit Sie geänderte Verzeichnis.

PS: Beachten Sie auch, dass diese Zeile:

(mkdir -p dir; cd dir; pwd;) 

wird auch nicht das aktuelle Verzeichnis in der aktuellen Shell ändern, da Befehle in eckigen Klammern in einem Sub-Shell ausgeführt werden, während geschweiften Klammer für die Gruppierung nur verwendet werden.

+2

Wäre es besser/einfacher 'mkdir -p dir' zu verwenden, das nicht fehlschlägt, wenn das Verzeichnis vorhanden ist und nicht' rmdir dir', was fehlschlägt, wenn das Verzeichnis nicht existiert? –

+0

@ JonathanLeffler: Einverstanden, bearbeitet. – anubhava

0

Obwohl die Handbücher sagt:

{ list; } 

Placing a list of commands between curly braces causes the list to be executed in the current shell context. No subshell is created. 

es an einer Pipeline Platzierung noch auf einer Subshell platzieren würde.

{ list; } | something 

Seit

Each command in a pipeline is executed in its own subshell (see Command Execution Environment). 

Die Befehle in { } würde sich nicht auf einer Subshell platziert werden, aber die höheren Kontext es selbst das ist sein würde, warum es immer noch die gleichen sein würden.

Als Test () und { } laufen würde genau das gleiche:

# echo "$BASHPID" 
6582 
# (ps -p "$BASHPID" -o ppid=) | cat 
6582 
# { ps -p "$BASHPID" -o ppid=; } | cat 
6582 

Beide sendet Elternprozess als rufende Shell.

Verwandte Themen