Ist es möglich, eine Shell-Befehlsersetzung ohne Verwendung einer Subshell durchzuführen?

11

Ich habe ein Szenario, in dem eine Befehlsersetzung ohne Verwendung einer Subshell erforderlich ist. Ich habe ein Konstrukt wie dieses:

pushd $(mktemp -d)

Jetzt möchte ich das temporäre Verzeichnis auf einmal beenden und entfernen:

rmdir $(popd)

Dies funktioniert jedoch nicht, weil popddas gepoppte Verzeichnis nicht zurückgegeben wird (es gibt das neue, jetzt aktuelle Verzeichnis zurück) und weil es in einer Subshell ausgeführt wird.

Etwas wie

dirs -l -1 ; popd &> /dev/null

gibt das gepoppte Verzeichnis zurück, kann aber nicht wie folgt verwendet werden:

rmdir $(dirs -l -1 ; popd &> /dev/null)

weil das popdnur die Subshell betrifft. Was erforderlich ist, ist die Fähigkeit, dies zu tun:

rmdir { dirs -l -1 ; popd &> /dev/null; }

aber das ist ungültige Syntax. Ist es möglich, diesen Effekt zu erzielen?

(Hinweis: Ich weiß, dass ich das temporäre Verzeichnis in einer Variablen speichern kann. Ich habe versucht, dies zu vermeiden und dabei etwas Neues zu lernen!)

Sternenhimmel
quelle
1
Ich würde das Verzeichnis in einer Variablen speichern. Auf diese Weise kann ein trapHandler das Verzeichnis bereinigen, falls der Prozess durch ein Signal ausgelöst wird.
Thrig
Die Antwort ist nein .
h0tw1r3
Es sieht so aus, als würde on fish, das Äquivalent zur Befehlssubstitution, ()den Ordner der äußeren Shell ändern. Es ist normalerweise nervig, aber in solchen Fällen ist es sicher nützlich, da bin ich mir sicher.
Trysis

Antworten:

10

Die Wahl des Titels Ihrer Frage ist etwas verwirrend.

pushd/ popd, eine cshvon bashund kopierte Funktion zsh, ist eine Möglichkeit, einen Stapel gespeicherter Verzeichnisse zu verwalten.

pushd /some/dir

Verschiebt das aktuelle Arbeitsverzeichnis auf einen Stapel und ändert dann das aktuelle Arbeitsverzeichnis (und druckt dann, /some/dirgefolgt vom Inhalt dieses Stapels (durch Leerzeichen getrennt).

popd

druckt den Inhalt des Stapels (wieder durch Leerzeichen getrennt) und wechselt dann zum obersten Element des Stapels und entfernt es vom Stapel.

(Beachten Sie auch, dass einige Verzeichnisse dort mit ihrer ~/xoder ihrer ~user/xNotation dargestellt werden).

Wenn der Stack derzeit /aund hat /b, ist das aktuelle Verzeichnis /hereund Sie führen:

 pushd /tmp/whatever
 popd

pushdwird gedruckt /tmp/whatever /here /a /bund popdausgegeben /here /a /b, nicht /tmp/whatever. Das ist unabhängig von der Verwendung der Befehlssubstitution oder nicht. popdkann nicht verwendet werden, um den Pfad des vorherigen Verzeichnisses abzurufen, und im Allgemeinen kann seine Ausgabe nicht nachbearbeitet werden (siehe das $dirstackoder $DIRSTACKArray einiger Shells für den Zugriff auf die Elemente dieses Verzeichnisstapels).

Vielleicht möchten Sie:

pushd "$(mktemp -d)" &&
popd &&
rmdir "$OLDPWD"

Oder

cd "$(mktemp -d)" &&
cd - &&
rmdir "$OLDPWD"

Allerdings würde ich verwenden:

tmpdir=$(mktemp -d) || exit
(
  cd "$tmpdir" || exit # in a subshell 
  # do what you have to do in that tmpdir
)
rmdir "$tmpdir"

Auf jeden Fall pushd "$(mktemp -d)"läuft nicht pushdin einer Subshell. In diesem Fall konnte das Arbeitsverzeichnis nicht geändert werden. Das mktempläuft in einer Unterschale. Da es sich um einen separaten Befehl handelt, muss er in einem separaten Prozess ausgeführt werden. Es schreibt seine Ausgabe in eine Pipe und der Shell-Prozess liest sie am anderen Ende der Pipe.

ksh93 kann den separaten Prozess vermeiden, wenn der Befehl eingebaut ist, aber selbst dort handelt es sich immer noch um eine Subshell (eine andere Arbeitsumgebung), die dieses Mal emuliert wird, anstatt sich auf die separate Umgebung zu verlassen, die normalerweise durch Forking bereitgestellt wird. Zum Beispiel ist in ksh93, a=0; echo "$(a=1; echo test)"; echo "$a"keine Gabel beteiligt, sondern echo "$a"gibt immer noch aus 0.

Wenn Sie die Ausgabe von mktempin einer Variablen speichern möchten, während Sie sie gleichzeitig pushdmit übergeben zsh, können Sie Folgendes tun:

pushd ${tmpdir::="$(mktemp -d)"}

Mit anderen Bourne-ähnlichen Muscheln:

unset tmpdir
pushd "${tmpdir=$(mktemp -d)}"

Oder um die Ausgabe $(mktemp -d)mehrmals zu verwenden, ohne sie explizit in einer Variablen zu speichern, können Sie zshanonyme Funktionen verwenden:

(){pushd ${1?} && cd - && rmdir $1} "$(mktemp -d)"
Stéphane Chazelas
quelle
Ich verstehe pushdund popdarbeite so, wie Sie es beschreiben und dass sie unabhängig von der Befehlsersetzung sind - keine Verwirrung! Sie antworteten jedoch durch eigenes Problem, indem Sie enthüllten $OLDPWD. Ich kann tun popd; rmdir $OLDPWD. Das ist die Antwort auf mein Problem - alles andere bestätigt nur, was ich dachte. Die Befehlssubstitution scheint ein Weg zu sein, dies zu lösen, aber es liegt nicht an der Subshell, und Sie können die Befehlssubstitution nicht ohne Subshell durchführen. Vielen Dank, dass Sie OLDPWD enthüllt haben - genau das brauche ich!
Sternenhimmel
@starfry, rmdir $(popd)bewirkt , dass popdes in einer Unter-Shell ausgeführt wird, was bedeutet, dass das aktuelle Verzeichnis nicht geändert wird. Selbst wenn es nicht in einer Unter-Shell ausgeführt wurde, ist die Ausgabe von popdnicht das temporäre Verzeichnis, sondern eine durch Leerzeichen getrennte Liste von Verzeichnissen ohne dieses temporäre Verzeichnis. Wo ich sage, dass Sie verwirrt sind.
Stéphane Chazelas
aber ich dachte, ich hätte das in meiner Frage gesagt: "popd gibt das gepoppte Verzeichnis nicht zurück (es gibt das neue, jetzt aktuelle Verzeichnis zurück) und auch, weil es in einer Subshell ausgeführt wird." Zugegeben, es gibt eine Liste zurück, aber die erste in der Liste ist die, auf die ich mich bezog.
Sternenhimmel
@starfry, OK sorry. Nehmen wir an, ich war derjenige, der durch den Titel der Frage verwirrt war, und habe den Rest nicht sorgfältig genug gelesen.
Stéphane Chazelas
Nun, deine Antwort war immer noch gut und hat mich in die richtige Richtung gelenkt. Ich wusste nie über diese Shell-Variable OLDPWD. Ich muss daran denken, eines Tages man bashvon Ende zu Ende zu lesen !
Sternenhimmel
2

Sie können die Verknüpfung des Verzeichnisses zuerst aufheben, bevor Sie es verlassen:

rmdir "$(pwd -P)" && popd

oder

rmdir "$(pwd -P)" && cd ..   # yes, .. is still usable

aber beachten Sie, dass pushdund popdsind wirklich Werkzeuge für die interaktive Shell, nicht für Skripte (deshalb sind sie so gesprächig, echte Skriptbefehle schweigen , wenn sie Erfolg haben ).

Toby Speight
quelle
0

dirsGeben Sie in bash eine Liste der gespeicherten Verzeichnisse mit der pushd / popd-Methode an.

Außerdem dirs -1druckt das letzte Verzeichnis in der Liste enthalten.

pushd $(mktmp -d)Verwenden Sie zum Entfernen des zuvor durch Ausführen erstellten Verzeichnisses Folgendes :

rmdir $(dirs -1)

Und dann popddas bereits entfernte Verzeichnis aus der Liste:

popd > /dev/null

Alles in einer Zeile:

rmdir $(dirs -1); popd > /dev/null

Und fügen Sie die Option (-l) hinzu, um die Notation ~/xoder zu vermeiden ~user/x:

rmdir $(dirs -l -1); popd > /dev/null

Das ist bemerkenswert ähnlich zu der Zeile, nach der Sie gefragt haben.
Außer dass ich nicht verwenden würde, &>da es jede Fehlermeldung von verbergen würde popd.

Hinweis: Das Verzeichnis bleibt rmdirwie pwdan diesem Punkt erhalten. Und wird nach dem popdBefehl tatsächlich getrennt (keine verbleibenden Links verwendet).


Es gibt eine Option für Shells, die die Variable "OLDPWD" unterstützen (die meisten bourneähnlichen Shells: ksh, bash, zsh have $OLDPWD). Es ist interessant festzustellen, dass ksh standardmäßig keine dirs, pod, pushd implementiert (lksh, dash und andere haben auch kein popd verfügbar, können also hier nicht verwendet werden):

popd && rmdir "$OLDPWD"

Oder idiomatischer (gleiche Liste von Muscheln wie oben):

popd && rmdir ~-
Gemeinschaft
quelle
Das Problem damit und was ich zu lösen versuchte, ist, dass Sie nicht rmdirdas aktuelle Verzeichnis verwenden können, in dem Sie sich dabei befinden würden. Sie müssen das ausführen, popdbevor Sie das tun, rmdiraber Sie müssen wissen, was zu entfernen ist, und popddas wird Ihnen nicht gesagt. Ich dachte, wie Sie, dirs -l -1war die Antwort, habe aber inzwischen herausgefunden, dass die Antwort tatsächlich zu verwenden ist $OLDPWD.
Sternenfrucht
@starfry Ich glaube, dass die kürzeste Antwort zu verwenden ist : popd && rmdir ~-.
@starfry Sie können das aktuelle Verzeichnis problemlos entfernen, sofern es leer ist (ohne das Löschen zu erzwingen). Sie könnten sogar gut anrufen rmdir "$PWD". Außerdem sollte das aktuelle Verzeichnis dasjenige sein, das von erstellt wurde mktmp -d(falls pushd $(mktemp -d)es zuvor ausgeführt wurde) und in das pushddas pwd verschoben wird. Also, ja, nach pushd und vor popd wird das erstellte Verzeichnis gespeichert $(dirs -1), ich sehe kein Problem.