È possibile eseguire la sostituzione dei comandi della shell senza usare una subshell?


11

Ho uno scenario che richiede la sostituzione dei comandi senza usare una subshell. Ho un costrutto come questo:

pushd $(mktemp -d)

Ora voglio uscire e rimuovere la directory temporanea in una volta sola:

rmdir $(popd)

Tuttavia, ciò non funziona perché popdnon restituisce la directory pop-up (restituisce la nuova directory, ora corrente) e anche perché viene eseguita in una subshell.

Qualcosa di simile a

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

restituirà la directory spuntata ma non può essere utilizzata in questo modo:

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

perché popdinfluenzerà solo la subshell. Ciò che è richiesto è la capacità di fare questo:

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

ma questa è sintassi non valida. È possibile ottenere questo effetto?

(nota: so di poter salvare la directory temporanea in una variabile; stavo cercando di evitare la necessità di farlo e di imparare qualcosa di nuovo nel processo!)


1
Vorrei salvare la directory in una variabile; in questo modo, un trapgestore può ripulire la directory nel caso in cui il processo venga sollecitato da un segnale.
thrig

La risposta è no .
h0tw1r3,

Sembra che fish, l'equivalente della sostituzione dei comandi (), cambi la cartella della shell esterna. Di solito è fastidioso, ma in casi come questo è utile, ne sono sicuro.
trysis,

Risposte:


10

La scelta del titolo della tua domanda è un po 'confusa.

pushd/ popd, una cshfunzione copiata da bashe zsh, è un modo per gestire una pila di directory memorizzate.

pushd /some/dir

spinge la directory di lavoro corrente su uno stack, quindi cambia la directory di lavoro corrente (e quindi stampa /some/dirseguita dal contenuto di quello stack (separato da spazio).

popd

stampa il contenuto della pila (di nuovo, spazio separato), quindi passa all'elemento superiore della pila e lo estrae dalla pila.

(attenzione anche che alcune directory saranno rappresentate lì con la loro ~/xo ~user/xnotazione).

Quindi, se lo stack ha attualmente /ae /b, la directory corrente è /heree stai eseguendo:

 pushd /tmp/whatever
 popd

pushdstamperà /tmp/whatever /here /a /be popdprodurrà /here /a /b, no /tmp/whatever. Questo è indipendente dall'uso della sostituzione dei comandi o meno. popdnon può essere usato per ottenere il percorso della directory precedente, e in generale il suo output non può essere post elaborato (vedere l' array $dirstacko $DIRSTACKdi alcune shell anche se per accedere agli elementi di quello stack di directory)

Forse vuoi:

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

O

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

Tuttavia, vorrei usare:

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

In ogni caso, pushd "$(mktemp -d)"non viene eseguito pushdin una subshell. In tal caso, non è possibile modificare la directory di lavoro. È mktempquello che gira in una subshell. Poiché è un comando separato, deve essere eseguito in un processo separato. Scrive l'output su una pipe e il processo shell lo legge sull'altra estremità della pipe.

ksh93 può evitare il processo separato quando il comando è incorporato, ma anche lì, è ancora una subshell (un ambiente di lavoro diverso) che questa volta viene emulato piuttosto che fare affidamento sull'ambiente separato normalmente fornito dal fork. Ad esempio, in ksh93, a=0; echo "$(a=1; echo test)"; echo "$a"non è coinvolto alcun fork, ma vengono comunque echo "$a"emessi 0.

Qui, se si desidera archiviare l'output di mktempin una variabile, contemporaneamente al passaggio a pushd, con zsh, è possibile:

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

Con altre conchiglie simili a Bourne:

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

O per utilizzare l'output di $(mktemp -d)più volte senza memorizzarlo esplicitamente in una variabile, è possibile utilizzare zshfunzioni anonime:

(){pushd ${1?} && cd - && rmdir $1} "$(mktemp -d)"

Capisco pushde popdlavoro come descrivi e che sono indipendenti dalla sostituzione dei comandi - non c'è confusione lì! Tuttavia, hai risposto con il proprio problema rivelando $OLDPWD. Posso fare popd; rmdir $OLDPWD. Questa è la risposta al mio problema: tutto il resto conferma ciò che ho pensato. La sostituzione dei comandi sembra essere un modo per risolverlo, ma non è a causa della subshell e non puoi fare la sostituzione dei comandi senza una subshell, quindi grazie per aver rivelato OLDPWD - questo è esattamente ciò di cui ho bisogno!
Starfry,

@starfry, rmdir $(popd)causa popdl'esecuzione in una sotto-shell, il che significa che non cambierà la directory corrente, ma anche se non è stata eseguita in una subshell, l'output di popdnon sarà la directory temporanea, sarà un elenco separato da spazi delle directory che non includono quella directory temporanea. È qui che sto dicendo che sei confuso.
Stéphane Chazelas,

ma pensavo di averlo detto nella mia domanda: "popd non restituisce la directory spuntata (restituisce la nuova directory, ora attuale) e anche perché viene eseguita in una subshell". Certo, restituisce un elenco, ma il primo nell'elenco è quello a cui mi riferivo.
Starfry,

@starfry, OK scusa. Diciamo che ero quello confuso dal titolo della domanda e non ho letto il resto abbastanza attentamente.
Stéphane Chazelas,

bene la tua risposta era ancora buona e mi ha indicato la giusta direzione. Non ho mai saputo di quella variabile di shell OLDPWD. Devo ricordare di aver letto un giorno dall'inizio man bashalla fine!
Starfry,

2

È possibile scollegare prima la directory, prima di lasciarla:

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

o

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

Ma nota che pushde popdsono davvero strumenti per la shell interattive, non per gli script (è per questo che sono così loquace; i comandi reali di scripting sono in silenzio quando ci riescono).


0

In bash, dirsfornire un elenco di directory memorizzate con il metodo pushd / popd.

Inoltre, dirs -1stampa l'ultima directory inclusa nell'elenco.

Quindi, per rimuovere la directory creata in precedenza eseguendo pushd $(mktmp -d), utilizzare:

rmdir $(dirs -1)

E quindi, popdla directory già rimossa dall'elenco:

popd > /dev/null

Tutto in una riga:

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

E aggiungendo l'opzione (-l) per evitare la notazione ~/xo ~user/x:

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

Che è notevolmente simile alla linea che hai chiesto.
Solo che non userei &>in quanto nasconderebbe qualsiasi segnalazione di errore popd.

Nota: la directory rimarrà dopo rmdircome è pwda quel punto. E sarà effettivamente scollegato dopo il popdcomando (nessun link rimanente utilizzato).


C'è un'opzione da usare, per le shell che supportano la variabile "OLDPWD" (la maggior parte delle shell simili a bourne: ksh, bash, zsh hanno $OLDPWD). È interessante notare che ksh non implementa dirs, pod, pushd di default (anche lksh, dash e altri non hanno popd disponibile, quindi non sono utilizzabili qui):

popd && rmdir "$OLDPWD"

O, più idiomatico (stesso elenco di shell come sopra):

popd && rmdir ~-

Il problema con questo, e quello che stavo cercando di risolvere, è che non puoi rmdiraccedere alla directory corrente che è dove ti saresti trovato mentre lo facevi. È necessario effettuare le operazioni popdprima di eseguire le operazioni, rmdirma è necessario sapere cosa rimuovere e popdnon è necessario. Come te, ho pensato dirs -l -1fosse la risposta, ma da allora ho scoperto che la risposta è effettivamente da usare $OLDPWD.
Starfry,

@starfry Credo che la risposta più breve è quello di utilizzare: popd && rmdir ~-.

@starfry Potresti effettivamente rimuovere la directory corrente, nessun problema, a condizione che sia vuota (senza forzare la cancellazione). Potresti anche chiamare rmdir "$PWD"tutto bene. Inoltre, la directory corrente dovrebbe essere quella creata da mktmp -d(se è pushd $(mktemp -d)stata eseguita in precedenza) e su cui pushdsposta pwd. Quindi, sì, dopo pushd e prima di popd, la directory creata è archiviata $(dirs -1), non riesco a vedere alcun problema.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.