185 votos

Ejecutar un subshell bash interactivo con comandos iniciales sin volver al ("super") Shell inmediatamente

Quiero ejecutar una subshell de bash, (1) ejecutar algunos comandos, (2) y luego permanecer en esa subshell para hacer lo que me plazca. Puedo hacer cada uno de estos individualmente:

  1. Ejecutar el comando con -c flag:

    $> bash -c "ls; pwd; <other commands...>"

    sin embargo, vuelve inmediatamente al "super" Shell después de ejecutar los comandos. También puedo simplemente ejecutar un subshell interactivo:

  2. Iniciar nuevo bash proceso:

    $> bash

    y no sale del subshell hasta que yo lo diga explícitamente... pero no puedo ejecutar ningún comando inicial. La solución más cercana que he encontrado es:

    $> bash -c "ls; pwd; <other commands>; exec bash"

    que funciona, pero no de la manera que yo quería, ya que ejecuta los comandos dados en un subshell, y luego abre uno separado para la interacción.

Quiero hacer esto en una sola línea. Una vez que salgo del subshell, debería volver al "super"Shell normal sin incidentes. Debe haber una manera~~

NB: Lo que no estoy pidiendo...

  1. no preguntar donde conseguir la página man de bash
  2. no preguntar cómo leer los comandos de inicialización de un archivo... Sé cómo hacer esto, no es la solución que estoy buscando
  3. no me interesa usar tmux o gnu screen
  4. no está interesado en dar contexto a esto. Es decir, la pregunta pretende ser general, y no para ningún propósito específico
  5. si es posible, quiero evitar el uso de soluciones que más o menos logran lo que quiero, pero de una manera "sucia". Sólo quiero hacer esto en una sola línea. En particular, no quiero hacer algo como xterm -e 'ls'

0 votos

Puedo imaginar una solución de Expect, pero difícilmente es la de una sola línea que quieres. ¿De qué manera es el exec bash ¿una solución inadecuada para usted?

0 votos

@glennjackman lo siento, no estoy familiarizado con la jerga. ¿Qué es una "solución de expectativa"? Además, el exec bash la solución implica dos subshell separados. Quiero una subcapa continua.

7 votos

La belleza de exec es que sustituye a el primer subshell con el segundo, por lo que sólo queda 1 Shell por debajo del padre. Si tus comandos de inicialización establecen variables de entorno, éstas existirán en el Shell ejecutado.

146voto

Jonathan Potter Puntos 191

Esto se puede hacer fácilmente con tubos temporales con nombre :

bash --init-file <(echo "ls; pwd")

El mérito de esta respuesta corresponde al comentario de Mentira Ryan . Esto me pareció muy útil, y se nota menos en los comentarios, así que pensé que debía ser su propia respuesta.

21 votos

Esto significa, presumiblemente, que $HOME/.bashrc no se ejecuta sin embargo. Tendría que incluirse desde la tubería de nombre temporal.

18 votos

Para aclarar, algo así: bash --init-file <(echo ". \"$HOME/.bashrc\"; ls; pwd")

10 votos

Esto es muy asqueroso pero funciona. No puedo creer que bash no soporte esto directamente.

22voto

ExpoBi Puntos 117

Intenta esto en su lugar:

$> bash -c "ls;pwd;other commands;$SHELL"

$SHELL Hace que el Shell se abra en modo interactivo, esperando un cierre con exit .

23 votos

Para tu información, esto abre una nueva Shell a continuación, por lo que si alguno de los comandos afecta al estado de la Shell actual (por ejemplo, el abastecimiento de un archivo) podría no funcionar como se espera

1 votos

@ThiefMaster puedes evitar fácilmente este comportamiento (si no lo deseas) envolviendo el comando init con un subshell: bash -c "(ls;pwd;other commands;);$SHELL"

1 votos

Buen consejo, pero cierto sólo en parte, @gagallo7; las variables de entorno se heredan "sólo hacia abajo", y como eso será un Shell hijo, el padre no verá ninguna asignación de env var. Ver comentarios en la pregunta original para el uso de exec para retener env vars (aunque probablemente pierde todo lo que tu comentario preservaría).

19voto

Eduardo Ivanec Puntos 7938

Esto se puede hacer de forma indirecta con un archivo temporal, aunque se necesitarán dos líneas:

echo "ls; pwd" > initfile
bash --init-file initfile

2 votos

Para conseguir un buen efecto puedes hacer que el archivo temporal se elimine por sí mismo incluyendo rm $BASH_SOURCE en él.

0 votos

Eduardo, gracias. Es una buena solución, pero... ¿estás diciendo que esto no se puede hacer sin tener que manipular la E/S de los archivos. Hay razones obvias por las que preferiría mantener esto como un comando autónomo, porque en el momento en que los archivos entran en la mezcla tendré que empezar a preocuparme de cómo hacer archivos temporales al azar y luego, como has mencionado, borrarlos. Simplemente requiere mucho más esfuerzo de esta manera si quiero ser riguroso. De ahí el deseo de una solución más minimalista y elegante.

0 votos

Utilice la utilidad mktemp para crear un archivo temporal único.

6voto

marshally Puntos 2260

La "solución Expect" a la que me refería es programar un bash Shell con el Esperar el lenguaje de programación :

#!/usr/bin/env expect
set init_commands [lindex $argv 0]
set bash_prompt {\$ $}              ;# adjust to suit your own prompt
spawn bash
expect -re $bash_prompt {send -- "$init_commands\r"}
interact
puts "exiting subshell"

Lo harías como: ./subshell.exp "ls; pwd"

0 votos

Supongo que esto tendría la ventaja de registrar los comandos en el historial también, ¿me equivoco? También tengo curiosidad por saber si bashrc/profile se ejecuta en este caso?

0 votos

Confirmado que esto permite poner comandos en el historial, cosa que las otras soluciones no hacen. Esto es genial para iniciar un proceso en un archivo .screenrc -- si sales del proceso iniciado, no cierras la ventana de la pantalla.

6voto

drdaeman Puntos 3312

¿Por qué no utilizar subsuelos nativos?

$ ( ls; pwd; exec $BASH; )
bar     foo     howdy
/tmp/hello/
bash-4.4$ 
bash-4.4$ exit
$

Encerrar los comandos con paréntesis hace que bash genere un subproceso para ejecutar estos comandos, por lo que puede, por ejemplo, alterar el entorno sin afectar al padre Shell. Esto es básicamente un equivalente más legible al bash -c "ls; pwd; exec $BASH" .

Si eso sigue pareciendo verboso, hay dos opciones. Una es tener este fragmento como una función:

$ run() { ( eval "$@"; exec $BASH; ) }
$ run 'ls; pwd;'
bar     foo     howdy
/tmp/hello/
bash-4.4$ exit
$ run 'ls;' 'pwd;'
bar     foo     howdy
/tmp/hello/
bash-4.4$ exit
$

Otra es hacer exec $BASH más corta:

$ R() { exec $BASH; }
$ ( ls; pwd; R )
bar     foo     howdy
/tmp/hello/
bash-4.4$ exit
$

Personalmente me gusta R se acercan más, ya que no es necesario jugar con las cadenas de escape.

1 votos

No estoy seguro de si hay alguna advertencia sobre el uso de exec para el escenario que el OP tiene en mente, pero para mí esta es la mejor solución propuesta, ya que utiliza comandos bash sin problemas de escape de cadenas.

EnMiMaquinaFunciona.com

EnMiMaquinaFunciona es una comunidad de administradores de sistemas en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros sysadmin, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X