Est-il possible d'effectuer une substitution de commandes shell sans utiliser de sous-shell?

11

J'ai un scénario qui appelle à la substitution de commandes sans utiliser de sous-shell. J'ai une construction comme celle-ci:

pushd $(mktemp -d)

Maintenant, je veux quitter et supprimer le répertoire temporaire en une seule fois:

rmdir $(popd)

Cependant, cela ne fonctionne pas car popdne renvoie pas le répertoire popped (il retourne le nouveau répertoire, maintenant actuel) et aussi parce qu'il est effectué dans un sous-shell.

Quelque chose comme

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

retournera le répertoire popped mais il ne peut pas être utilisé comme ceci:

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

car popdcela n'affectera que le sous-shell. Ce qu'il faut, c'est la capacité de le faire:

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

mais c'est une syntaxe invalide. Est-il possible d'obtenir cet effet?

(note: je sais que je peux enregistrer le répertoire temporaire dans une variable; j'essayais d'éviter de le faire et d'apprendre quelque chose de nouveau dans le processus!)

étoilé
la source
1
Je sauvegarderais le répertoire dans une variable; de cette façon, un trapgestionnaire peut nettoyer le répertoire si le processus est poussé par un signal.
thrig
La réponse est non .
h0tw1r3
Il ressemble à fish, l'équivalent de la substitution de commandes (), change le dossier du shell externe. C'est généralement ennuyeux, mais dans des cas comme celui-ci, c'est utile, j'en suis sûr.
trysis

Réponses:

10

Le choix du titre de votre question est un peu déroutant.

pushd/ popd, une cshfonctionnalité copiée par bashet zsh, est un moyen de gérer une pile de répertoires mémorisés.

pushd /some/dir

pousse le répertoire de travail en cours sur une pile, puis modifie le répertoire de travail en cours (puis imprime /some/dirsuivi du contenu de cette pile (séparé par des espaces).

popd

imprime le contenu de la pile (là encore, séparé par des espaces), puis passe à l'élément supérieur de la pile et le fait disparaître de la pile.

(attention également à ce que certains répertoires y seront représentés avec leur notation ~/xou ~user/x).

Donc, si la pile contient actuellement /aet /b, le répertoire actuel est /hereet vous exécutez:

 pushd /tmp/whatever
 popd

pushdimprimera /tmp/whatever /here /a /bet popdsortira /here /a /b, non /tmp/whatever. C'est indépendant de l'utilisation de la substitution de commandes ou non. popdne peut pas être utilisé pour obtenir le chemin du répertoire précédent, et en général, sa sortie ne peut pas être post-traitée (voir le tableau $dirstackou $DIRSTACKde certains shells pour accéder aux éléments de cette pile de répertoires)

Peut-être que vous voulez:

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

Ou

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

Cependant, j'utiliserais:

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

Dans tous les cas, pushd "$(mktemp -d)"ne s'exécute pas pushden sous-shell. Si tel était le cas, il ne pourrait pas modifier le répertoire de travail. Voilà mktempqui fonctionne en sous-coquille. Comme il s'agit d'une commande distincte, elle doit s'exécuter dans un processus distinct. Il écrit sa sortie sur un tuyau et le processus shell la lit à l'autre extrémité du tuyau.

ksh93 peut éviter le processus séparé lorsque la commande est intégrée, mais même là, c'est toujours un sous-shell (un environnement de travail différent) qui cette fois est émulé plutôt que de s'appuyer sur l'environnement séparé normalement fourni par la fourche. Par exemple, dans ksh93, a=0; echo "$(a=1; echo test)"; echo "$a", aucune fourchette est impliquée, mais encore echo "$a"sorties 0.

Ici, si vous voulez stocker la sortie de mktempdans une variable, en même temps que vous la passez à pushd, avec zsh, vous pouvez faire:

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

Avec d'autres coques de type Bourne:

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

Ou pour utiliser $(mktemp -d)plusieurs fois la sortie sans la stocker explicitement dans une variable, vous pouvez utiliser zshdes fonctions anonymes:

(){pushd ${1?} && cd - && rmdir $1} "$(mktemp -d)"
Stéphane Chazelas
la source
Je comprends pushdet popdtravaille comme vous le décrivez et qu'ils sont indépendants de la substitution de commande - pas de confusion là-bas! Cependant, vous avez répondu par votre propre problème en révélant $OLDPWD. Je peux faire popd; rmdir $OLDPWD. C'est la réponse à mon problème - tout le reste confirme simplement ce que je pensais. La substitution de commandes semble être un moyen de le résoudre, mais ce n'est pas à cause du sous-shell et vous ne pouvez pas faire de substitution de commandes sans sous-shell, alors merci d'avoir révélé OLDPWD - c'est exactement ce dont j'ai besoin!
Starfry
@starfry, rmdir $(popd)fait popdtourner dans un sous-shell ce qui signifie qu'il ne changera pas le répertoire courant, mais même s'il ne s'est pas exécuté dans un sous-shell, la sortie de popdne sera pas le répertoire temporaire, ce sera une liste séparée par des espaces des répertoires n'incluant pas ce répertoire temporaire. C'est là que je dis que vous êtes confus.
Stéphane Chazelas
mais je pensais l'avoir dit dans ma question: "popd ne retourne pas le répertoire popped (il retourne le nouveau répertoire, maintenant courant) et aussi parce qu'il est exécuté dans un sous-shell." Certes, elle renvoie une liste, mais la première de la liste est celle à laquelle je faisais référence.
Starfry
@starfry, OK désolé. Disons que je suis celui qui est confus par le titre de la question et que je n'ai pas lu le reste assez attentivement.
Stéphane Chazelas
eh bien ta réponse était toujours bonne et m'a pointé dans la bonne direction. Je n'ai jamais connu cette variable shell OLDPWD. Je dois penser à lire un jour man bashde bout en bout!
Starfry
2

Vous pouvez d'abord dissocier le répertoire avant de le quitter:

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

ou

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

mais notez que pushdet popdsont vraiment des outils pour les shells interactifs, pas pour les scripts (c'est pourquoi ils sont si bavards; les vraies commandes de script sont silencieuses quand elles réussissent).

Toby Speight
la source
0

En bash, dirsfournissez une liste des répertoires mémorisés par la méthode pushd / popd.

Imprime également dirs -1le dernier répertoire inclus dans la liste.

Donc, pour supprimer le répertoire créé précédemment en exécutant pushd $(mktmp -d), utilisez:

rmdir $(dirs -1)

Et puis, popdle répertoire déjà supprimé de la liste:

popd > /dev/null

Tout en une ligne:

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

Et en ajoutant l'option (-l) pour éviter la notation ~/xou ~user/x:

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

Ce qui est remarquablement similaire à la ligne que vous avez demandée.
Sauf que je ne l'utiliserais pas &>car cela cacherait tout rapport d'erreur de popd.

Remarque: le répertoire restera après rmdirtel qu'il est pwdà ce point. Et sera effectivement dissocié après la popdcommande (aucun lien restant utilisé).


Il existe une option à utiliser, pour les shells qui supportent la variable "OLDPWD" (la plupart des shells de type bourne: ksh, bash, zsh have $OLDPWD). Il est intéressant de noter que ksh n'implémente pas les répertoires, pod, pushd par défaut (lksh, dash et autres n'ont pas non plus de popd disponibles, donc ne sont pas utilisables ici):

popd && rmdir "$OLDPWD"

Ou, plus idiomatique (même liste de coques que ci-dessus):

popd && rmdir ~-
Communauté
la source
Le problème avec cela, et ce que j'essayais de résoudre, c'est que vous ne pouvez pas rmdirle répertoire actuel qui est où vous seriez lorsque vous faites cela. Vous devez effectuer le popdavant de faire le rmdirmais vous devez savoir quoi supprimer et popdne vous le dit pas. Comme vous, je pensais que dirs -l -1c'était la réponse, mais j'ai découvert depuis que la réponse est en fait à utiliser $OLDPWD.
Starfry
@starfry Je crois que la réponse la plus courte est à l' utilisation: popd && rmdir ~-.
@starfry Vous pouvez réellement supprimer le répertoire courant, pas de problème, à condition qu'il soit vide (sans forcer l'effacement). Vous pourriez même appeler rmdir "$PWD"bien. De plus, le répertoire courant doit être celui créé par mktmp -d(s'il a pushd $(mktemp -d)été exécuté au préalable) et vers lequel pushdse déplace le pwd. Donc, oui, après pushd et avant popd, le répertoire créé est stocké dans $(dirs -1), je ne vois aucun problème.