¿Es posible realizar la sustitución de comandos de shell sin usar un subshell?

11

Tengo un escenario que requiere la sustitución de comandos sin usar una subshell. Tengo una construcción como esta:

pushd $(mktemp -d)

Ahora quiero salir y eliminar el directorio temporal de una vez:

rmdir $(popd)

Sin embargo, eso no funciona porque popdno devuelve el directorio emergente (devuelve el nuevo directorio, ahora actual) y también porque se realiza en una subshell.

Algo como

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

devolverá el directorio emergente pero no se puede usar así:

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

porque popdsolo afectará a la subshell. Lo que se requiere es la capacidad de hacer esto:

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

pero esa es una sintaxis inválida. ¿Es posible lograr este efecto?

(nota: ¡Sé que puedo guardar el directorio temporal en una variable; estaba tratando de evitar la necesidad de hacerlo y aprender algo nuevo en el proceso!)

starfry
fuente
1
Guardaría el directorio en una variable; de esa manera, un trapcontrolador puede limpiar el directorio en caso de que el proceso sea activado por una señal.
thrig
La respuesta es no .
h0tw1r3
Parece que en fish, el equivalente de la sustitución de comandos (), cambia la carpeta del shell externo. Suele ser molesto, pero en casos como este es útil, estoy seguro.
trysis

Respuestas:

10

La elección del título de su pregunta es un poco confusa.

pushd/ popd, una cshcaracterística copiada por bashy zsh, es una forma de administrar una pila de directorios recordados.

pushd /some/dir

empuja el directorio de trabajo actual a una pila y luego cambia el directorio de trabajo actual (y luego imprime /some/dirseguido por el contenido de esa pila (separados por espacios).

popd

imprime el contenido de la pila (de nuevo, separados por espacios) y luego cambia al elemento superior de la pila y lo saca de la pila.

(también tenga en cuenta que algunos directorios estarán representados allí con su ~/xo ~user/xnotación).

Entonces, si la pila tiene actualmente /ay /b, el directorio actual es /herey está ejecutando:

 pushd /tmp/whatever
 popd

pushdse imprimirá /tmp/whatever /here /a /by popdsaldrá /here /a /b, no /tmp/whatever. Eso es independiente de usar la sustitución de comandos o no. popdno se puede usar para obtener la ruta del directorio anterior y, en general, su salida no se puede procesar posteriormente (consulte la matriz $dirstacko $DIRSTACKde algunos shells para acceder a los elementos de esa pila de directorios)

Quizás quieras:

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

O

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

Sin embargo, usaría:

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

En cualquier caso, pushd "$(mktemp -d)"no se ejecuta pushden una subshell. Si lo hizo, no podría cambiar el directorio de trabajo. Eso se mktempejecuta en una subshell. Dado que es un comando separado, debe ejecutarse en un proceso separado. Escribe su salida en una tubería, y el proceso de shell lo lee en el otro extremo de la tubería.

ksh93 puede evitar el proceso por separado cuando el comando está integrado, pero incluso allí, sigue siendo una subshell (un entorno de trabajo diferente) que esta vez se emula en lugar de depender del entorno separado que normalmente proporciona la bifurcación. Por ejemplo, en ksh93, a=0; echo "$(a=1; echo test)"; echo "$a"no hay una bifurcación involucrada, pero aún se echo "$a"emite 0.

Aquí, si desea almacenar la salida mktempen una variable, al mismo tiempo que se le pasa a pushd, con zsh, usted podría hacer:

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

Con otras conchas tipo Bourne:

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

O para usar la salida de $(mktemp -d)varias veces sin almacenarla explícitamente en una variable, puede usar zshfunciones anónimas:

(){pushd ${1?} && cd - && rmdir $1} "$(mktemp -d)"
Stéphane Chazelas
fuente
Entiendo pushdy popdtrabajo como lo describe y que son independientes de la sustitución de comandos, ¡no hay confusión! Sin embargo, respondiste por tu propio problema revelando $OLDPWD. Que puedo hacer popd; rmdir $OLDPWD. Esa es la respuesta a mi problema: todo lo demás solo confirma lo que pensaba. La sustitución de comandos parece ser una forma de resolverlo, pero no se debe a la subshell y no se puede hacer la sustitución de comandos sin una subshell, así que gracias por revelar OLDPWD, ¡eso es exactamente lo que necesito!
starfry
@starfry, rmdir $(popd)hace popdque se ejecute en un sub-shell, lo que significa que no cambiará el directorio actual, pero incluso si no se ejecutó en un subshell, la salida de popdno será el directorio temporal, será una lista separada por espacios de directorios que no incluyen ese directorio temporal. Que es donde estoy diciendo que estás confundido.
Stéphane Chazelas
pero pensé que había dicho eso en mi pregunta: "popd no devuelve el directorio emergente (devuelve el nuevo directorio, ahora actual) y también porque se realiza en una subshell". Es cierto que devuelve una lista, pero la primera en la lista es a la que me refería.
starfry
@starfry, ok lo siento. Digamos que estaba confundido por el título de la pregunta y no leí el resto con suficiente cuidado.
Stéphane Chazelas
bueno, tu respuesta aún fue buena y me señaló en la dirección correcta. Nunca supe de esa variable de shell OLDPWD. ¡Debo recordar leer un día man bashde punta a punta!
Starfry
2

Puede desvincular el directorio primero, antes de abandonarlo:

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

o

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

pero tenga en cuenta que pushdy popdrealmente son herramientas para shells interactivos, no para scripts (es por eso que son tan habladores; los comandos de scripting reales son silenciosos cuando tienen éxito).

Toby Speight
fuente
0

En bash, dirsproporcione una lista de directorios recordados por el método pushd / popd.

Además, dirs -1imprime el último directorio incluido en la lista.

Entonces, para eliminar el directorio creado previamente mediante la ejecución pushd $(mktmp -d), use:

rmdir $(dirs -1)

Y luego, popdel directorio ya eliminado de la lista:

popd > /dev/null

Todo en una linea:

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

Y agregando la opción (-l) para evitar la notación ~/xo ~user/x:

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

Lo cual es notablemente similar a la línea que solicitó.
Excepto que no lo usaría, &>ya que ocultaría cualquier informe de error popd.

Nota: El directorio permanecerá después rmdirya que es el pwden ese punto. Y se desvinculará después del popdcomando (no se utilizarán los enlaces restantes).


Hay una opción para usar, para los shells que admiten la variable "OLDPWD" (la mayoría de los shells tipo bourne: ksh, bash, zsh have $OLDPWD). Es interesante notar que ksh no implementa directorios, pod, pushd por defecto (lksh, dash y otros tampoco tienen popd disponible, por lo tanto, no se pueden usar aquí):

popd && rmdir "$OLDPWD"

O, más idiomático (la misma lista de conchas que el anterior):

popd && rmdir ~-
Comunidad
fuente
El problema con esto, y lo que estaba tratando de resolver, es que no puedes encontrar rmdirel directorio actual que es donde estarías al hacer esto. Necesita realizar el popdantes de hacer el rmdirpero necesita saber qué eliminar y popdno le dice eso. Yo, como tú, pensé que dirs -l -1era la respuesta, pero desde entonces descubrí que la respuesta es realmente usar $OLDPWD.
Starfry
@starfry Creo que la respuesta más corta es de uso: popd && rmdir ~-.
@starfry En realidad, podría eliminar el directorio actual, no hay problema, siempre que esté vacío (sin forzar el borrado). Incluso podrías llamar rmdir "$PWD"bien. Además, el directorio actual debe ser el creado por mktmp -d(si pushd $(mktemp -d)se ejecutó de antemano) y al que pushdmueve el pwd. Entonces, sí, después pushd y antes de popd, el directorio creado se almacena $(dirs -1), no veo ningún problema.